
 //
 // ocipl.cpp
 //
 // Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Martin Fuchs <martin-fuchs@gmx.net>
 //
 // OCIPL Version 1.7
 //

 /// \file ocipl.cpp
 /// OCIPL implementation file


/*

  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:

  * Redistributions of source code must retain the above copyright
	notice, this list of conditions and the following disclaimer.
  * Redistributions in binary form must reproduce the above copyright
	notice, this list of conditions and the following disclaimer in
	the documentation and/or other materials provided with the
	distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  POSSIBILITY OF SUCH DAMAGE.

*/

#ifndef OCIPL_NO_COMMENT
#define OCIPL_NO_COMMENT	// no #pragma comment(lib, ...) statements in .lib files -> free selection of orasql library
#endif

#define _olint	// for #define CONST const
#include "ocipl.h"

#include <sstream>
#include <locale>

#include <assert.h>
#include <time.h>


#ifndef _MSC_VER
char* strupr(char* s)
{
	for(char*p=s; *p; ++p)
		*p = toupper(*p);

	return s;
}
#endif

size_t max_strlen(const char* s, size_t max_len)
{
	if (!s)
		return 0;

	size_t cnt = 0;

	while(cnt<max_len && *s)
		++cnt, ++s;

	return cnt;
}

char* max_strdup(const char* s, size_t max_len)
{
	if (!s)
		return NULL;

	size_t l = max_strlen(s, max_len);
	char* ret = (char*) malloc(l+1);

#ifdef __STDC_WANT_SECURE_LIB__
	strncpy_s(ret, l+1, s, l);
#else
	strncpy(ret, s, l);
#endif
	ret[l] = '\0';

	return ret;
}


namespace OCIPL {


 // We use bulk fetch with 100 rows per default.
int g_OCIPL_BULK_ROWS = 100;


void throw_oci_exception(OCIError* errh, sword res)
{
	switch(res) {
	  case OCI_ERROR:
	  case OCI_NO_DATA:
		THROW_OCI_EXCEPTION(errh);
		break;

	  case OCI_INVALID_HANDLE:
		THROW_OCI_EXCEPTION("invalid handle");
		break;

		//@todo handle other error states

	}
}

void throw_oci_exception(OCIEnv* envh, sword res)
{
	switch(res) {
	  case OCI_ERROR:
	  case OCI_NO_DATA:
	  case OCI_INVALID_HANDLE:
		THROW_OCI_EXCEPTION(envh);

		//@todo handle other error states

	}
}


OciException::OciException(OCIEnv* envh, const char* file, int line)
{
	OraText buffer[1024];

	sb4 errorcode;
	sword res = OCIErrorGet(envh, 1, NULL, &errorcode, buffer, sizeof(buffer), OCI_HTYPE_ENV);

	if (res == OCI_SUCCESS) {
		_sql_error_code = errorcode;
		_msg = max_strdup((const char*)buffer, COUNTOF(buffer));
	} else {
		_sql_error_code = 0;
		_msg = NULL;
	}

	_file = file;
	_line = line;
	_parse_offset = 0;
}

OciException::OciException(OCIError* errh, const char* file, int line)
{
	TCHAR buffer[1024];

	sb4 errorcode;
	sword res = OCIErrorGet(errh, 1, NULL, &errorcode, (OraText*)buffer, sizeof(buffer), OCI_HTYPE_ERROR);

	if (res == OCI_SUCCESS) {
		_sql_error_code = errorcode;
#ifdef _UNICODE
		char nbuffer[2048];
#ifdef _MSC_VER
		std::_USE(std::locale::empty(), std::ctype<wchar_t>)
#else
		std::use_facet<std::ctype<wchar_t> >(std::locale::empty())
#endif
				.narrow((const TCHAR*)buffer, (const TCHAR*)buffer+_tcslen((const TCHAR*)buffer)+1, 0, nbuffer);
		_msg = max_strdup(nbuffer, COUNTOF(nbuffer));
#else
		_msg = max_strdup(buffer, COUNTOF(buffer));
#endif
	} else {
		_sql_error_code = 0;
		_msg = NULL;
	}

	_file = file;
	_line = line;
	_parse_offset = 0;
}

OciException::OciException(OCIError* errh, SqlStatement& stmt, const char* file, int line)
{
	char buffer[4096];

	sb4 errorcode;
#ifdef _UNICODE
	TCHAR wbuffer[4096];

	sword res = OCIErrorGet(errh, 1, NULL, &errorcode, (OraText*)wbuffer, sizeof(wbuffer), OCI_HTYPE_ERROR);

#ifdef _MSC_VER
		std::_USE(std::locale::empty(), std::ctype<wchar_t>)
#else
		std::use_facet<std::ctype<wchar_t> >(std::locale::empty())
#endif
				.narrow((const TCHAR*)wbuffer, (const TCHAR*)wbuffer+_tcslen((const TCHAR*)wbuffer)+1, 0, buffer);
#else
	sword res = OCIErrorGet(errh, 1, NULL, &errorcode, (OraText*)buffer, sizeof(buffer), OCI_HTYPE_ERROR);
#endif

	ub4 size = sizeof(_parse_offset);
	if (OCIAttrGet(stmt, OCI_HTYPE_STMT, &_parse_offset, &size, OCI_ATTR_PARSE_ERROR_OFFSET, errh) != OCI_SUCCESS)
		_parse_offset = 0;

	if (res == OCI_SUCCESS)
		_sql_error_code = errorcode;
	else {
		*buffer = '\0';
		_sql_error_code = 0;
	}

	_last_sql = stmt.get_last_sql();

	_file = file;
	_line = line;

	if (!_last_sql.empty()) {
		file = stmt.get_last_file();

		 // copy source code position from statement if available
		if (file) {
			_file = file;
			_line = stmt.get_last_line();
		}

		const TCHAR* sql = _last_sql.c_str();
		size_t l = max_strlen(buffer, COUNTOF(buffer));

		int line = 1;
		const TCHAR* s = sql;
		const TCHAR* e = sql + _parse_offset;
		const TCHAR* lp = sql;

		while(s < e)
			if (*s == '\n') {
				++line;

				if (*++s == '\r')
					++s;

				lp = s;
			} else
				++s;

		size_t column = 1 + (s-lp);

#ifdef __STDC_WANT_SECURE_LIB__
		char* b = buffer+l + _snprintf_s(buffer+l, sizeof(buffer)-l, _TRUNCATE, "\nerror at SQL position %d [%d.%d]:\n", _parse_offset, line, column);
#else
		char* b = buffer+l + _snprintf(buffer+l, sizeof(buffer)-l-1, "\nerror at SQL position %d [%d.%d]:\n", _parse_offset, line, column);
#endif
		char* p = b;
		s = lp;
		while(*s && *s!='\n' && p<buffer+sizeof(buffer)-3)
			*p++ = *s++;

		*p++ = '\n';

		b = p;
		const TCHAR* err_ptr = sql + _parse_offset;
		s = lp;
		for(; s<err_ptr && p<buffer+sizeof(buffer)-2; ++s) {
			if (*s=='\n' || *sql=='\r')
				p = b;
			else if (*s == '\t')
				*p++ = '\t';
			else
				*p++ = ' ';
		}

		*p++ = '^';
		*p = '\0';

		l = max_strlen(buffer, COUNTOF(buffer));
#ifdef __STDC_WANT_SECURE_LIB__
		_snprintf_s(buffer+l, sizeof(buffer)-l, _TRUNCATE, "\n\nlast SQL statement:\n%s", sql);
#else
		_snprintf(buffer+l, sizeof(buffer)-l, "\n\nlast SQL statement:\n%s", sql);
#endif
	}

	_msg = max_strdup(buffer, COUNTOF(buffer));
}

OciException::OciException(const char* msg, const char* file, int line)
{
	_msg = _strdup(msg);
	_file = file;
	_line = line;
}

OciException::OciException(const OciException& other)
{
	_sql_error_code = other._sql_error_code;
	_msg = _strdup(other._msg);
	_file = other._file;
	_line = other._line;
	_last_sql = other._last_sql;
}


OciException::~OciException() throw()
{
	free(_msg);
}


#ifndef _NO_ESQL

#include <sql2oci.h>

#ifdef _MSC_VER
#if ORACLE_VERSION>=110
#pragma comment(lib, "orasql11")
#elif ORACLE_VERSION>=100
#pragma comment(lib, "orasql10")
#elif ORACLE_VERSION>=90
#pragma comment(lib, "orasql9")
#elif ORACLE_VERSION>=81	// no orasql library for Oracle 8.0 available
#pragma comment(lib, "orasql8")
#endif
#endif

#if ORACLE_VERSION>=81	// no SQLEnvGet() and SQLSvcCtxGet() for Oracle 8.0.x
OciConnection* OciConnection::CreateFromESqlContext(void* context)
{
	OCIEnv* envhp = NULL;
	sword res = SQLEnvGet(context, &envhp);

	if (res != OCI_SUCCESS)
		return NULL;

	OCISvcCtx* svchp = NULL;
	res = SQLSvcCtxGet(context, NULL, 0, &svchp);

	oci_check_error(envhp, res);

	return new OciConnection(envhp, svchp);
}
#endif

#endif // _NO_ESQL


 /// cancel a pending OCI call in the worker thread
void OciConnection::cancel()
{
	sword res = OCIBreak(_ctx, _env._errh);

	oci_check_error(_env._errh, res);
}

#if ORACLE_VERSION>=81
 /// reset() is to be called after handling the cancellation in the calling worker thread.
void OciConnection::reset()
{
	sword res = OCIReset(_ctx, _env._errh);

	oci_check_error(_env._errh, res);
}
#endif


 // commit the current transaction
void OciContext::commit(ub4 flags)
{
	LocalOciState state(*this, OS_TRANSACTION);

	sword res = OCITransCommit(_handle, _env._errh, flags);
	oci_check_error(_env, res);

	_trans_cnt = 0;
}

 // rollback the current transaction
void OciContext::rollback(ub4 flags)
{
	LocalOciState state(*this, OS_TRANSACTION);

	sword res = OCITransRollback(_handle, _env._errh, flags);
	oci_check_error(_env, res);

	_trans_cnt = 0;
}


SqlString& SqlString::operator=(const TCHAR* s)
{
	size_t l = s? _tcslen(s): 0;
	if (l > _blen)
		resize(l);	// resize for longer new strings

	_tcscpy_s(_str, _blen+1, s?s:TEXT(""));
	_ind = l>0? 0: -1;
	return *this;
}

SqlString& SqlString::operator=(const tstring& s)
{
	if (!s.empty()) {
		size_t l = s.length();
		if (_blen < l)
			resize(l);	// resize for longer new strings

		memcpy(_str, s.c_str(), (l+1)*sizeof(char));
		_ind = 0;
	} else {
		*_str = '\0';
		_ind = -1;
	}

	return *this;
}


int number_to_int(const oraclenumber& val, OCIError* errh)
{
	int x;

#if ORACLE_VERSION>=81
	 // Oracle Version >= 8.1.x
	boolean isInt;
	sword res = OCINumberIsInt(errh, (OCINumber*)&val, &isInt);

	if (res==OCI_SUCCESS && isInt)
	{
		res = OCINumberToInt(errh, (OCINumber*)&val, sizeof(x), OCI_NUMBER_SIGNED, &x);
		oci_check_error(errh, res);
	}
	else
#else
	 // OCI.DLL of Oracle Version 8.0
	sword res = OCINumberToInt(errh, (OCINumber*)&val, sizeof(x), OCI_NUMBER_SIGNED, &x);

	if (res != OCI_SUCCESS)
#endif
	{
		double d = number_to_double(val, errh);

		x = int(d + .5);
	}

	//x = atoi(str(true).c_str());

	return x;
}

#ifdef _MSC_VER
LONGLONG number_to_int64(const oraclenumber& val, OCIError* errh, int null)
{
	LONGLONG x;

	sword res = OCINumberToInt(errh, (OCINumber*)&val, sizeof(x), OCI_NUMBER_SIGNED, &x);
	oci_check_error(errh, res);

	return x;
}
#endif

double number_to_double(const oraclenumber& val, OCIError* errh)
{
	double x;

	sword res = OCINumberToReal(errh, (OCINumber*)&val, sizeof(x), &x);
	oci_check_error(errh, res);

	return x;
}

tstring number_to_str(const oraclenumber& val, OCIError* errh, const TCHAR* fmt, const TCHAR* nls_fmt)
{
	OraText buffer[128];
	ub4 l = sizeof(buffer);

	sword res = OCINumberToText(errh,
						(OCINumber*)&val,
						(const OraText*)fmt, (ub4)_tcslen(fmt)*sizeof(TOraText),
						(const OraText*)nls_fmt, (ub4)_tcslen(nls_fmt)*sizeof(TOraText),
						&l, buffer);

	oci_check_error(errh, res);

//	const TCHAR* p = (const TCHAR*)buffer;
//	while(istspace(*p))
//		++p;

	return (const TCHAR*)buffer;//p
}


SqlNumber::SqlNumber(const oraclenumber* pnum)
{
	memcpy(&_val.exp, &pnum->exp, pnum->len);
	_val.len = pnum->len;
	_ind.set();
}

SqlNumber::SqlNumber(OCIError* errh, const TCHAR* str, const TCHAR* fmt, const TCHAR* nls_fmt)
{
	if (str) {
		while(istspace(*str))
			++str;

		if (*str) {
			sword res = OCINumberFromText(errh,
								(const OraText*)str, (ub4)_tcslen(str)*sizeof(TOraText),
								(const OraText*)fmt, (ub4)_tcslen(fmt)*sizeof(TOraText),
								(const OraText*)nls_fmt, (ub4)_tcslen(nls_fmt)*sizeof(TOraText),
								(OCINumber*)&_val);

			oci_check_error(errh, res);

			_ind.set();
		} else
		_ind.clear();
	} else
		_ind.clear();
}


/*
Oracle stores values of the NUMBER datatype in a variable-length format.
The first byte is the exponent and is followed by 1 to 20 mantissa bytes.
The high-order bit of the exponent byte is the sign bit; it is set for
positive numbers and it is cleared for negative numbers. The lower 7 bits
represent the exponent, which is a base-100 digit with an offset of 65.

To calculate the decimal exponent, add 65 to the base-100 exponent and
add another 128 if the number is positive. If the number is negative, you
do the same, but subsequently the bits are inverted.
For example, -5 has a base-100 exponent = 62 (0x3e). The decimal exponent
is thus (~0x3e) -128 - 65 = 0xc1 -128 -65 = 193 -128 -65 = 0.

Each mantissa byte is a base-100 digit, in the range 1 to 100.
For positive numbers, the digit has 1 added to it. So, the mantissa
digit for the value 5 is 6. For negative numbers, instead of adding 1
the digit is subtracted from 101. So, the mantissa digit for the
number -5 is 96 (101 - 5). Negative numbers have a byte containing 102
appended to the data bytes. However, negative numbers that have
20 mantissa bytes do not have the trailing 102 byte. Because the
mantissa digits are stored in base-100, each byte can represent two
decimal digits. The mantissa is normalized; leading zeroes are not stored.
Up to 20 data bytes can represent the mantissa. However, only 19 are
guaranteed to be accurate. The 19 data bytes, each representing
a base-100 digit, yield a maximum precision of 38 digits for an
internal datatype NUMBER.
*/

/*
tstring SqlNumber::str(bool internal) const
{
	int sign = _val.exp & 0x80;
	int exp;

	tstring ret = sign? internal?TEXT(" "):TEXT(""): TEXT("-");

	if (sign) {
		if (_val.exp == 0x80)
			return TEXT("0");

		exp = (_val.exp & 0x7F) - 65;
	} else
		exp = 62 - (_val.exp & 0x7F);

	if (exp < 0) {
		ret.append(TEXT("0."));

		for(int e=exp; ++e<0; )
			ret.append(1, '0');
	}

	const ub1* p = _val.mant;

	if (sign) {
		for(int n=_val.len; ; ) {
			int x = *p++ - 1;

			if (internal || x/10 || !ret.empty())	// don't print leading zeros
				ret.append(1, '0' + x/10);

			ret.append(1, '0' + x%10);

			--exp;
			if (--n <= 1)
				break;

			if (exp == -1)
				ret.append(1, '.');
		}
	} else {
		for(int n=_val.len; ; ) {
			int x = 101 - *p++;

			ret.append(1, '0' + x/10);
			ret.append(1, '0' + x%10);

			--exp;
			if (--n <= 2)
				break;

			if (exp == -1)
				ret.append(1, '.');
		}

		if (_val.len == 21) {
			int x = 101 - *p++;

			ret.append(1, '0' + x/10);
			ret.append(1, '0' + x%10);

			--exp;
		}// else
		//	assert(*p==102);
	}

	if (internal)
		for(; exp>=0; --exp)
			ret.append(TEXT("00"));
	else if (_tcschr(ret.c_str(), '.')) { // remove trailing zeros
		while(!ret.empty() && ret.at(ret.length()-1)=='0')
			ret.resize(ret.length()-1);

		if (!ret.empty() && ret.at(ret.length()-1)=='.')
			ret.resize(ret.length()-1);
	}

	return ret;
}
*/


tstring ocidate_to_string(const OCIDate& date)	// Usage of OCIDateToText would also be possible.
{
	TCHAR buffer[40];

#ifdef __STDC_WANT_SECURE_LIB__
	_stprintf_s(buffer, COUNTOF(buffer),
#else
	_stprintf(buffer,
#endif
				TEXT("%02d.%02d.%04d %02d:%02d:%02d"),
				date.OCIDateDD, date.OCIDateMM, date.OCIDateYYYY,
				date.OCIDateTime.OCITimeHH, date.OCIDateTime.OCITimeMI, date.OCIDateTime.OCITimeSS);

	return buffer;
}

tstring ocidate_to_short_string(const OCIDate& date)
{
	TCHAR buffer[40];

#ifdef __STDC_WANT_SECURE_LIB__
	_stprintf_s(buffer, COUNTOF(buffer),
#else
	_stprintf(buffer,
#endif
				TEXT("%02d.%02d.%04d"),
				date.OCIDateDD, date.OCIDateMM, date.OCIDateYYYY);

	return buffer;
}

struct tm* ocidate_to_gmt(const OCIDate& date)
{
	struct tm ltime = {
		date.OCIDateTime.OCITimeSS,
		date.OCIDateTime.OCITimeMI,
		date.OCIDateTime.OCITimeHH,
		date.OCIDateDD,
		date.OCIDateMM - 1,
		date.OCIDateYYYY - 1900,
		-1,
		-1,
		-1
	};

	time_t time = mktime(&ltime);

#ifdef __STDC_WANT_SECURE_LIB__
	struct tm newtime;
	gmtime_s(&newtime, &time);
	struct tm* gmt = &newtime;
#else
	struct tm* gmt = gmtime(&time);
#endif

	return gmt;
}

struct tm* ocidate_to_lctime(const OCIDate& date)
{
	struct tm ltime = {
		date.OCIDateTime.OCITimeSS,
		date.OCIDateTime.OCITimeMI,
		date.OCIDateTime.OCITimeHH,
		date.OCIDateDD,
		date.OCIDateMM - 1,
		date.OCIDateYYYY - 1900,
		-1,
		-1,
		-1
	};

	time_t time = mktime(&ltime);

#ifdef __STDC_WANT_SECURE_LIB__
	struct tm newtime;
	localtime_s(&newtime, &time);
	struct tm* lct = &newtime;
#else
	struct tm* lct = localtime(&time);
#endif

	return lct;
}

time_t ocidate_to_time_t(const OCIDate& date)
{
	time_t time = mktime(ocidate_to_lctime(date));

	return time;
}

#ifdef _WIN32
bool ocidate_to_systemtime(const OCIDate& date, SYSTEMTIME* pSysTime)
{
	SYSTEMTIME stime;

	stime.wYear = date.OCIDateYYYY;
	stime.wMonth = date.OCIDateMM;
	stime.wDayOfWeek = 0;
	stime.wDay = date.OCIDateDD;
	stime.wHour = date.OCIDateTime.OCITimeHH;
	stime.wMinute = date.OCIDateTime.OCITimeMI;
	stime.wSecond = date.OCIDateTime.OCITimeSS;
	stime.wMilliseconds = 0;

	 // Validate date and fill the wDayOfWeek field
	FILETIME ftime;

	if (!SystemTimeToFileTime(&stime, &ftime))
		return false;

	if (!FileTimeToSystemTime(&ftime, pSysTime))
		return false;

	return true;
}
#endif


tstring ocidatetime_to_string(const ocidatetime& date)
{
	TCHAR buffer[40];

#ifdef __STDC_WANT_SECURE_LIB__
	_stprintf_s(buffer, COUNTOF(buffer),
#else
	_stprintf(buffer,
#endif
				TEXT("%02d.%02d.%04d %02d:%02d:%02d"),
				date.day, date.month, 100*(date.century-100)+(date.year-100),
				date.hour-1, date.minute-1, date.second-1);

	return buffer;
}


OciDate::OciDate(const OCIDate& date, OCIInd ind)
 :	SqlValue(ind)
{
	OCIDate::operator=(date);
}

OciDate::OciDate(OCIError* errh, const TCHAR* str, const TCHAR* fmt)
{
	if (str) {
		while(istspace(*str))
			++str;

		if (*str) {
			sword res = OCIDateFromText(errh, (const OraText*)str, (ub4)_tcslen(str)*sizeof(TOraText),
											  (const OraText*)fmt, (ub4)_tcslen(fmt)*sizeof(TOraText), NULL/*lang*/, 0, this);

			oci_check_error(errh, res);

			_ind.set();
		} else
			_ind.clear();
	} else
		_ind.clear();
}

OciDate::OciDate(struct tm* t)
{
	OCIDateYYYY = t->tm_year + 1900;
	OCIDateMM = t->tm_mon + 1;
	OCIDateDD = t->tm_mday;
	OCIDateTime.OCITimeHH = t->tm_hour;
	OCIDateTime.OCITimeMI = t->tm_min;
	OCIDateTime.OCITimeSS = t->tm_sec;

	ind().set();
}

int OciDate::compare(const OciDate& other) const
{
	if (OCIDateYYYY < other.OCIDateYYYY)
		return -1;
	else if (OCIDateYYYY > other.OCIDateYYYY)
		return 1;

	if (OCIDateMM < other.OCIDateMM)
		return -1;
	else if (OCIDateMM > other.OCIDateMM)
		return 1;

	if (OCIDateDD < other.OCIDateDD)
		return -1;
	else if (OCIDateDD > other.OCIDateDD)
		return 1;

	if (OCIDateTime.OCITimeHH < other.OCIDateTime.OCITimeHH)
		return -1;
	else if (OCIDateTime.OCITimeHH > other.OCIDateTime.OCITimeHH)
		return 1;

	if (OCIDateTime.OCITimeMI < other.OCIDateTime.OCITimeMI)
		return -1;
	else if (OCIDateTime.OCITimeMI > other.OCIDateTime.OCITimeMI)
		return 1;

	if (OCIDateTime.OCITimeSS < other.OCIDateTime.OCITimeSS)
		return -1;
	else if (OCIDateTime.OCITimeSS > other.OCIDateTime.OCITimeSS)
		return 1;

	return 0;
}


tstring ColumnTypeInfo::get_type_str(bool show_null) const
{
	tostringstream str;

	switch(_data_type) {
	  case OCI_TYPECODE_CHAR:
		str << TEXT("CHAR(") << _width << TEXT(")");
		break;

	  case OCI_TYPECODE_VARCHAR:
		str << TEXT("VARCHAR(") << _width << TEXT(")");
		break;

	  case OCI_TYPECODE_VARCHAR2:
		str << TEXT("VARCHAR2(") << _width << TEXT(")");
		break;

	  case OCI_TYPECODE_NUMBER:
		if (_precision == 0)
			str << TEXT("NUMBER");
		else if (_scale == -127) {
			if (_precision == 126)
				str << TEXT("FLOAT");
			else
				str << TEXT("FLOAT(") << (int)_precision << TEXT(")");
		} else if (_scale)
			str << TEXT("NUMBER(") << (int)_precision << TEXT(",") << (int)_scale << TEXT(")");
		else
			str << TEXT("NUMBER(") << (int)_precision << TEXT(")");
		break;

#ifdef SQLT_BFLOAT
	  case OCI_TYPECODE_BFLOAT:
		str << TEXT("BINARY_FLOAT");
		break;

	  case OCI_TYPECODE_BDOUBLE:
		str << TEXT("BINARY_DOUBLE");
		break;
#endif

	  case OCI_TYPECODE_DATE:
		str << TEXT("DATE");
		break;

#if ORACLE_VERSION>=81
	  case OCI_TYPECODE_TIMESTAMP:
		str << TEXT("TIMESTAMP");
		break;
#endif

	  case OCI_TYPECODE_BLOB:
		str << TEXT("BLOB");
		break;

	  case OCI_TYPECODE_BFILE:
		str << TEXT("BFILE");
		break;

	  case OCI_TYPECODE_CLOB:
		str << TEXT("CLOB");
		break;

	  case OCI_TYPECODE_CFILE:
		str << TEXT("CFILE");
		break;

	  case SQLT_LNG:
		str << TEXT("LONG");
		break;

	  default:
		str << TEXT("unknown");
	}

	if (show_null)
		if (!_nullable)
			str << TEXT(" NOT NULL");
		else
			str << TEXT(" NULL");

	return str.str();
}


SqlStatement::~SqlStatement()
{
	delete _res;
	_res = NULL;
}


#ifndef FORCE_CODEPOS
void SqlStatement::prepare(const tstring& sql, ub4 lang)
{
	prepare(sql, NULL, -1, lang);
}
#endif

void SqlStatement::prepare(const tstring& sql, const char* file, int line, ub4 lang)
{
	delete _res;
	_res = NULL;

	_last_sql = sql;
	_last_file = file;
	_last_line = line;

	LocalOciState state(_ctx, OS_PREPARE);

	sword res = OCIStmtPrepare(_handle/*stmtp*/, _env._errh, (OraText*)sql.c_str(), (ub4)sql.length()*sizeof(TOraText), lang, OCI_DEFAULT);
	oci_check_error(_env, res);

	_state = PREPARED;
	_stmt_type = -1;
	_result_buffers = 0;
}


#if ORACLE_VERSION>=90

#ifndef FORCE_CODEPOS
void SqlCacheStmt::prepare(const tstring& sql, ub4 lang)
{
	prepare(sql, NULL, -1, lang);
}
#endif

void SqlCacheStmt::prepare(const tstring& sql, const char* file, int line, ub4 lang)
{
	if (!_tag.empty())
		release();

	tstring& tag = _stmt_cache[sql];

	if (tag.empty()) {
		TCHAR buffer[16];
#ifdef __STDC_WANT_SECURE_LIB__
		_stprintf_s(buffer, COUNTOF(buffer),
#else
		_stprintf(buffer,
#endif
		TEXT("%x"), _stmt_cache.size());
		tag = buffer;
	}

	_last_sql = sql;
	_last_file = file;
	_last_line = line;
	_tag = tag;

	LocalOciState state(_ctx, OS_PREPARE);

	sword res = OCIStmtPrepare2(_ctx, &_handle/*stmtp*/, _env._errh,
								(OraText*)sql.c_str(), (ub4)sql.length()*sizeof(TOraText),
								(OraText*)_tag.c_str(), (ub4)_tag.length()*sizeof(TOraText), lang, OCI_DEFAULT);
	oci_check_error(_env, res);

	_state = PREPARED;
	_stmt_type = -1;
	_result_buffers = 0;
}

void SqlCacheStmt::release()
{
	delete _res;
	_res = NULL;

	LocalOciState state(_ctx, OS_RELEASE);

	sword res = OCIStmtRelease(_handle, _env._errh, (OraText*)_tag.c_str(), (ub4)_tag.length()*sizeof(TOraText), OCI_DEFAULT);
	_handle = 0;

	oci_check_error(_env, res);
}

SqlCacheStmt::~SqlCacheStmt()
{
	release();
}

#endif


int SqlStatement::get_stmt_type() const
{
	if (_stmt_type == -1) {
		ub2 stmt_type;	// OCI_STMT_SELECT, OCI_STMT_UPDATE, ...
		ub4 size = sizeof(stmt_type);

		sword res = OCIAttrGet(_handle/*stmtp*/, get_type_id(), &stmt_type, &size, OCI_ATTR_STMT_TYPE, _env._errh);
		oci_check_error(_env, res);

		_stmt_type = stmt_type;
	}

	return _stmt_type;
}


ub4 SqlStatement::fetched_rows() const
{
/*
 if ORACLE_VERSION>=90
	ub4 row_count;
	ub4 size = sizeof(row_count);

	sword res = OCIAttrGet(_handle, get_type_id(), &row_count, &size, OCI_ATTR_ROWS_FETCHED, _env._errh);
	oci_check_error(_env, res);

	return row_count;
 else
*/
	return _last_fetched_row - _last_row;
}


ub4 SqlStatement::row_count() const
{
	ub4 row_count;
	ub4 size = sizeof(row_count);

	sword res = OCIAttrGet(_handle/*stmtp*/, get_type_id(), &row_count, &size, OCI_ATTR_ROW_COUNT, _env._errh);
	oci_check_error(_env, res);

	return row_count;
}


OCIDefine* SqlStatement::bind_result(int idx, DefinePar par, OCIDefine* defnp)
{
	sword res = OCIDefineByPos(_handle/*stmtp*/, &defnp, _env._errh, idx+1,
								par.valuep, par.value_sz, par.dty, par.indp,
								(ub2*)0, (ub2*)0, OCI_DEFAULT);
	oci_check_error(_env, res);

	if (_state & DEFINED)
		assert(_result_buffers==par.buffers);	// check if all bind variables have the same buffer count

	if (_state & EXECUTED) {
		delete _res;
		_res = NULL;

		_state = (_state|DEFINED) & ~(FETCHED|EOF_DATA);
	} else
		_state |= DEFINED;

	_result_buffers = par.buffers;

	return defnp;	// Implicitly allocated define handles are freed automatically with the statement handle.
}


void SqlStatement::execute(ub4 rows, ub4 mode)
{
	 // a SELECT statement without ready defined variables?
	if (!(_state&DEFINED) && get_stmt_type()==OCI_STMT_SELECT) {
		execute_internal(0, mode);

		 // define resultset variables
		assert(!_res);

		if (rows > 0) {
			_res = new SqlResult(*this, rows);
			fetch(rows);
		} else {
			_res = new SqlResult(*this, _bulk_rows);
		}
	} else
		execute_internal(rows, mode);
}


void SqlStatement::execute_fetch(ub4 mode)
{
	 // A SELECT statement without ready defined variables?
	if (!(_state&DEFINED) && get_stmt_type()==OCI_STMT_SELECT) {
		execute_internal(0, mode);

		 // define resultset variables
		assert(!_res);
		_res = new SqlResult(*this, _bulk_rows);

		fetch(_result_buffers);
	} else
		execute_internal(_result_buffers, mode);
}


void SqlStatement::execute_internal(ub4 rows, ub4 mode)
{
	int stmt_type = get_stmt_type();

	LocalOciState state(_ctx, OS_EXECUTE, stmt_type);

	if (rows==0 && stmt_type==OCI_STMT_SELECT) {
		 // Optimizing of TCP packets by transfering multiple datasets in one packet
		try {
			//set_attribute(OCI_ATTR_PREFETCH_MEMORY, 1500);
			set_attribute(OCI_ATTR_PREFETCH_ROWS, 10);
		} catch(std::exception&) {
			// ignore exception of this optional performance tuning
		}
	}

	 // execute and fetch
	sword res = OCIStmtExecute(_ctx, _handle, _env._errh, rows, 0/*rowoff*/,
								(CONST OCISnapshot*)0, (OCISnapshot*)0, mode);

	_last_row = 0;
	_last_fetched_row = row_count();

	if (res==OCI_NO_DATA && stmt_type>=OCI_STMT_SELECT && stmt_type<=OCI_STMT_INSERT) {
		_state |= EOF_DATA;

		// There are no additional rows pending to be fetched.
	} else if (res == OCI_ERROR) {
		_state = STMT_ERROR;
		//throw OCI_EXCEPTION(_env._errh, *this, CODEPOS);
		throw_ocipl_exception(OciException(_env._errh, *this, CODEPOS));
	} else {
		if (rows > 0)
			_state |= EXECUTED|FETCHED;
		else
			_state = (_state|EXECUTED) & ~(FETCHED|EOF_DATA);

		oci_check_error(_env, res);

		// There may be more rows available to be fetched (for queries) or the DML statement succeeded.
	}
}


void SqlStatement::fetch(ub4 rows/*=-1*/)
{
	if (!(_state & EXECUTED)) {
		if (rows == (ub4)-1)
			if (!(_state & DEFINED))
				rows = _bulk_rows;	// optimize using bulk fetch
			else
				rows = _result_buffers;

		execute(rows);
	} else {
		if (rows == (ub4)-1)
			rows = _result_buffers;

		LocalOciState state(_ctx, OS_FETCH);

#if ORACLE_VERSION>=90
		sword res = OCIStmtFetch2(_handle, _env._errh, rows, OCI_FETCH_NEXT, 0, OCI_DEFAULT);
#else
		sword res = OCIStmtFetch(_handle, _env._errh, rows, OCI_FETCH_NEXT, OCI_DEFAULT);
#endif

		_last_row = _last_fetched_row;
		_last_fetched_row = row_count();

		if (res != OCI_NO_DATA) {	// more data available or did there occur an error?
			_state |= FETCHED;
			oci_check_error(_env, res);

			// There may be more rows available to be fetched.
		} else {
			_state |= EOF_DATA;

			// There are no additional rows pending to be fetched.
		}
	}
}



void SqlStatement::get_column_type(int idx, ColumnType& info) const
{
	dvoid* parmdp;

	sword res = OCIParamGet(_handle, get_type_id(), _env._errh, &parmdp, idx+1);
	oci_check_error(_env, res);

	info.init(_env._errh, parmdp);

	res = OCIDescriptorFree(parmdp, OCI_DTYPE_PARAM);
	oci_check_error(_env, res);
}

void ColumnType::init(OCIError* errh, dvoid* handle)
{
	 // get column name
	TOraText* pcol_name = NULL;
	ub4 size = sizeof(pcol_name);

	sword res = OCIAttrGet(handle, OCI_DTYPE_PARAM, &pcol_name, &size, OCI_ATTR_NAME, errh);
	oci_check_error(errh, res);
	_name.assign((const TCHAR*)pcol_name, size);

	 // get column data type
	size = sizeof(_data_type);
	res = OCIAttrGet(handle, OCI_DTYPE_PARAM, &_data_type, &size, OCI_ATTR_DATA_TYPE, errh);
	oci_check_error(errh, res);

	 // get NULL-ability flag
	size = sizeof(_nullable);
	res = OCIAttrGet(handle, OCI_DTYPE_PARAM, &_nullable, &size, OCI_ATTR_IS_NULL, errh);
	oci_check_error(errh, res);

#if ORACLE_VERSION>=90
	 // retrieve the length semantics for the column
	size = sizeof(_char_semantics);
	res = OCIAttrGet(handle, OCI_DTYPE_PARAM, &_char_semantics, &size, OCI_ATTR_CHAR_USED, errh);
	if (res != OCI_SUCCESS)
		_char_semantics = 0;

	size = sizeof(_width);
	if (_char_semantics)
		 // get the column width in characters
		res = OCIAttrGet(handle, OCI_DTYPE_PARAM, &_width, &size, OCI_ATTR_CHAR_SIZE, errh);
	else
#endif
		 // get the column width in bytes
		res = OCIAttrGet(handle, OCI_DTYPE_PARAM, &_width, &size, OCI_ATTR_DATA_SIZE, errh);

	oci_check_error(errh, res);

	switch(_data_type) {
	  default: //case OCI_TYPECODE_VARCHAR:
		_precision = 0;
		_scale = -127;
		break;

	  case OCI_TYPECODE_NUMBER:
#ifdef SQLT_BFLOAT
	  case OCI_TYPECODE_BFLOAT:
	  case OCI_TYPECODE_BDOUBLE:
#endif
		size = sizeof(_precision);
		res = OCIAttrGet(handle, OCI_DTYPE_PARAM, &_precision, &size, OCI_ATTR_PRECISION, errh);
		oci_check_error(errh, res);

		size = sizeof(_scale);
		res = OCIAttrGet(handle, OCI_DTYPE_PARAM, &_scale, &size, OCI_ATTR_SCALE, errh);
		oci_check_error(errh, res);
		break;

	  case OCI_TYPECODE_DATE:
#if ORACLE_VERSION>=81
	  //case OCI_TYPECODE_TIME:
	  //case OCI_TYPECODE_TIMESTAMP:
#endif
		_precision = 0;
		_scale = -127;
		break;
	}
}


void SqlStatement::print_results(tostream& out, bool header)
{
	if (!(_state & EXECUTED))
		execute();
	else if (!(_state & FETCHED))
		fetch(_result_buffers);

	if (header)
		_res->print_columns(out);

	int row_cnt = 0;

	for(ResultIterator it(*this); it.has_next(); ++it) {
		++row_cnt;

		out << TEXT("row ") << row_cnt << TEXT(":\n");

		_res->print_values(out, it);

		out << TEXT("\n");
	}
}


SqlRecord::SqlRecord(SqlStatement& stmt, int rows)
 :	_env(stmt._env)
{
	stmt.ensure_execute();

	int column_cnt = stmt.get_column_count();

	_values.resize(column_cnt);
	ColumnVector::iterator it = _values.begin();

	for(int idx=0; idx<column_cnt; ++idx,++it) {
		stmt.get_column_type(idx, it->_type);

		it->_pvar = new SqlVariantArray(it->_type, rows);
	}

	//_name_map.clear();
}

SqlRecord::~SqlRecord()
{
	for(ColumnVector::iterator it=_values.begin(); it!=_values.end(); ++it)
		delete it->_pvar;
}

void SqlRecord::alloc_column(size_t col_idx, const ColumnTypeInfo& type, int rows)
{
	Data& data = _values[col_idx];

	 // (re)allocate variant data buffers
	delete data._pvar;
	data._pvar = new SqlVariantArray(type, rows);
}

const ColumnType& SqlRecord::get_column_type(const tstring& col_name) const
{
/*
	for(ColumnVector::const_iterator it=_values.begin(); it!=_values.end(); ++it)
		if (!stricmp(it->_type._name.c_str(), col_name.c_str()))
			return it->_type;
*/
	NameMap::const_iterator found = find_column(col_name);

	if (found != _name_map.end()) {
		int idx = found->second;
		return _values[idx]._type;
	} else {
		THROW_OCI_EXCEPTION("column not found");
		return *(ColumnType*)NULL;
	}
}

const SqlVariantArray& SqlRecord::get_column(const tstring& col_name) const
{
/*
	for(ColumnVector::const_iterator it=_values.begin(); it!=_values.end(); ++it)
		if (!stricmp(it->_type._name.c_str(), col_name.c_str()))
			return *it->_pvar;
*/
	NameMap::const_iterator found = find_column(col_name);

	if (found != _name_map.end()) {
		int idx = found->second;
		return *_values[idx]._pvar;
	} else {
		THROW_OCI_EXCEPTION("column not found");
		return *(SqlVariantArray*)NULL;
	}
}

SqlRecord::NameMap::const_iterator SqlRecord::find_column(const tstring& col_name) const
{
	tstring upper_name;

	 // initialize column name map
	if (_name_map.empty()) {
		int idx = 0;

		for(ColumnVector::const_iterator it=_values.begin(); it!=_values.end(); ++it,++idx) {
			upper_name = it->_type._name.c_str();
			_tcsupr_s((TCHAR*)upper_name.data(), upper_name.length()+1);

			_name_map[upper_name] = idx;
		}
	}

	upper_name = col_name;
	_tcsupr_s((TCHAR*)upper_name.data(), upper_name.length()+1);

	return _name_map.find(upper_name);
}


void SqlRecord::bind_result(SqlStatement& stmt)
{
	int idx = 0;

	for(ColumnVector::iterator it=_values.begin(); it!=_values.end(); ++it,++idx)
		it->_pvar->bind_result(stmt, idx);
}

void SqlRecord::print_columns(tostream& out)
{
	for(ColumnVector::const_iterator it=_values.begin(); it!=_values.end(); ++it)
		out << it->_type._name << TEXT(": ") << it->_type.get_type_str() << TEXT("\n");

	out << TEXT("\n");
}

void SqlRecord::print_values(tostream& out, int row)
{
	ColumnVector::const_iterator it_type = _values.begin();

	for(ColumnVector::const_iterator it=_values.begin(); it!=_values.end(); ++it,++it_type)
		out << it->_type._name << TEXT("=") << it->_pvar->str(row, _env._errh) << TEXT("\n");
}


SqlVariantArray::SqlVariantArray(const ColumnTypeInfo& type, int size)
 :	_size(size),
	_data_type(type._data_type)
{
	switch(type._data_type) {
	  case OCI_TYPECODE_CHAR:
		_data_type = OCI_TYPECODE_VARCHAR;
		// fall through

	  case OCI_TYPECODE_VARCHAR:
		_pstring = new SqlStringArray(size, type._width);
		break;

	  case OCI_TYPECODE_NUMBER:
		_pnumber = new SqlNumberArray(size);
		break;

#ifdef SQLT_BFLOAT
	  case OCI_TYPECODE_BFLOAT:
		_pfloat = new float[size];
		break;

	  case OCI_TYPECODE_BDOUBLE:
		_pdouble = new double[size];
		break;
#endif

	  case OCI_TYPECODE_DATE:
		_poci_date = new OciDateArray(size);
		break;

#if ORACLE_VERSION>=81
	  case OCI_TYPECODE_TIME:
	  case OCI_TYPECODE_TIMESTAMP:
		_psql_date = new SqlDateTimeArray(size);
		break;
/* no LOB allocation without available handles
	  case OCI_TYPECODE_BLOB:
		_pblob = new OciBlobArray(size);
		break;

	  case OCI_TYPECODE_CLOB:
		_pclob = new OciClobArray(size);
		break;
*/
#endif
	  case SQLT_LNG:
		_pstring = new SqlStringArray(size, type._width);	//@@
		break;

	  default:
		THROW_OCI_EXCEPTION("unsupported datatype");
	}
}

SqlVariantArray::SqlVariantArray(const SqlVariantArray& other)
 :	_size(0),
	_data_type(0)
{
	assign(other);
}

void SqlVariantArray::assign(const SqlVariantArray& other)
{
	clear();

	_size = other._size;
	_data_type = other._data_type;

	switch(other._data_type) {
	  case 0:
		break;

	  case OCI_TYPECODE_VARCHAR:
		_pstring = new SqlStringArray(*other._pstring);	//@@
		break;

	  case OCI_TYPECODE_NUMBER:
		_pnumber = new SqlNumberArray(*other._pnumber);
		break;

#ifdef SQLT_BFLOAT
	  case OCI_TYPECODE_BFLOAT:
		_pfloat = new float[_size];
		memcpy(_pfloat, other._pfloat, sizeof(float)*_size);
		break;

	  case OCI_TYPECODE_BDOUBLE:
		_pdouble = new double[_size];
		memcpy(_pfloat, other._pfloat, sizeof(double)*_size);
		break;
#endif

	  case OCI_TYPECODE_DATE:
		_poci_date = new OciDateArray(*other._poci_date);
		break;

#if ORACLE_VERSION>=81
	  case OCI_TYPECODE_TIME:
	  case OCI_TYPECODE_TIMESTAMP:
		_psql_date = new SqlDateTimeArray(*other._psql_date);
		break;
#endif
/*
	  case OCI_TYPECODE_BLOB:
		_pblob = new OciBlobArray(*other._pblob);
		break;

	  case OCI_TYPECODE_CLOB:
		_pclob = new OciClobArray(*other._pclob);
		break;
*/
	  case SQLT_LNG:
		_pstring = new SqlStringArray(*other._pstring);	//@@
		break;

	  default:
		assert(0);
	}
}

void SqlVariantArray::clear()
{
	switch(_data_type) {
	  case 0:
		break;

	  case OCI_TYPECODE_VARCHAR:
		delete _pstring;
		break;

	  case OCI_TYPECODE_NUMBER:
		delete _pnumber;
		break;

#ifdef SQLT_BFLOAT
	  case OCI_TYPECODE_BFLOAT:
		delete _pfloat;
		break;

	  case OCI_TYPECODE_BDOUBLE:
		delete _pfloat;
		break;
#endif

	  case OCI_TYPECODE_DATE:
		delete _poci_date;
		break;

#if ORACLE_VERSION>=81
	  case OCI_TYPECODE_TIME:
	  case OCI_TYPECODE_TIMESTAMP:
		delete _psql_date;
		break;
#endif
/*
	  case OCI_TYPECODE_BLOB:
		delete _pblob;
		break;

	  case OCI_TYPECODE_CLOB:
		delete _pclob;
		break;
*/
	  case SQLT_LNG:
		delete _pstring;	//@@
		break;

	  default:
		assert(0);
	}

	_size = 0;
	_data_type = 0;
}

tstring SqlVariantArray::str(int row, OCIError* errh) const
{
#ifdef SQLT_BFLOAT
	TCHAR buffer[16];
#endif

	switch(_data_type) {
	  case 0:
		return TEXT("");

	  case OCI_TYPECODE_VARCHAR:
		return _pstring->c_str(row);
//		return *_string;

	  case OCI_TYPECODE_NUMBER:
		return _pnumber->str(row, errh);

#ifdef SQLT_BFLOAT
	  case OCI_TYPECODE_BFLOAT:
#ifdef __STDC_WANT_SECURE_LIB__
		_stprintf_s(buffer, COUNTOF(buffer),
#else
		_stprintf(buffer,
#endif
		TEXT("%f"), _pfloat[row]);
		return buffer;

	  case OCI_TYPECODE_BDOUBLE:
#ifdef __STDC_WANT_SECURE_LIB__
		_stprintf_s(buffer, COUNTOF(buffer),
#else
		_stprintf(buffer,
#endif
		TEXT("%f"), _pdouble[row]);
		return buffer;
#endif

	  case OCI_TYPECODE_DATE:
		return _poci_date->str(row);

#if ORACLE_VERSION>=81
	  case OCI_TYPECODE_TIME:
	  case OCI_TYPECODE_TIMESTAMP:
		return _psql_date->str(row);
#endif
/*
	  case OCI_TYPECODE_BLOB:
		return TEXT("(BLOB)");

	  case OCI_TYPECODE_BFILE:
		return TEXT("(BFILE)");

	  case OCI_TYPECODE_CLOB:
		return TEXT("(CLOB)");

	  case OCI_TYPECODE_CFILE:
		return TEXT("(CFILE)");
*/
	  case SQLT_LNG:
		return _pstring->c_str(row);	//@@

	  default:
		assert(0);
		return TEXT("");
	}
}

void SqlVariantArray::set_string(int row, const TCHAR* str)
{
	if (_data_type != OCI_TYPECODE_VARCHAR)
		THROW_OCI_EXCEPTION("invalid variant data type");

	_pstring->assign(row, str);
}


SqlNumber SqlVariantArray::number(int row, OCIError* errh) const
{
	switch(_data_type) {
	  case 0:
		return SqlNumber();

	  case OCI_TYPECODE_VARCHAR:
		return SqlNumber(errh, _pstring->c_str(row));

	  case OCI_TYPECODE_NUMBER:
		if (_pnumber->is_not_null(row))
			return _pnumber->get_ref(row);
		else
			return SqlNumber();

#ifdef SQLT_BFLOAT
	  case OCI_TYPECODE_BFLOAT:
		return SqlNumber(errh, _pfloat[row]);

	  case OCI_TYPECODE_BDOUBLE:
		return SqlNumber(errh, _pdouble[row]);
#endif

	//case OCI_TYPECODE_DATE:
	// 	return ...

	  default:
		THROW_OCI_EXCEPTION("no conversion to number");
		//return SqlNumber();	// not reached
	}
}

void SqlVariantArray::set_number(int row, const SqlNumber& num)
{
	if (_data_type != OCI_TYPECODE_NUMBER)
		THROW_OCI_EXCEPTION("invalid variant data type");

	_pnumber->set_number(row, num);
}


OciDate SqlVariantArray::oci_date(int row, OCIError* errh) const
{
	switch(_data_type) {
	  case 0:
		return OciDate();

	  case OCI_TYPECODE_VARCHAR:
		return OciDate(errh, _pstring->c_str(row));

	  case OCI_TYPECODE_DATE:
		if (_poci_date->is_not_null(row))
	 		return *_poci_date->get_ref(row);
		else
			return OciDate();
/*@@todo convert type
#if ORACLE_VERSION>=81
	  case OCI_TYPECODE_TIME:
	  case OCI_TYPECODE_TIMESTAMP:
	 	return *_sql_date;
#endif
*/
	  default:
		THROW_OCI_EXCEPTION("no conversion to date");
		//return OciDate();	// not reached
	}
}

void SqlVariantArray::set_date(int row, const OciDate& date)
{
	if (_data_type != OCI_TYPECODE_DATE)
		THROW_OCI_EXCEPTION("invalid variant data type");

	_poci_date->set_date(row, date);
}


SqlDateTime SqlVariantArray::sql_date(int row, OCIError* errh) const
{
	switch(_data_type) {
	  case 0:
		return SqlDateTime();

/*@@todo convert type
	  case OCI_TYPECODE_VARCHAR:
		return SqlDateTime(errh, *_string);

	  case OCI_TYPECODE_DATE:
	 	return *_oci_date;
*/
#if ORACLE_VERSION>=81
	  case OCI_TYPECODE_TIME:
	  case OCI_TYPECODE_TIMESTAMP:
		if (_psql_date->is_not_null(row))
	 		return *_psql_date->get_ref(row);
		else
			return SqlDateTime();
#endif

	  default:
		THROW_OCI_EXCEPTION("no conversion to date");
		//return SqlDateTime();	// not reached
	}
}

void SqlVariantArray::set_datetime(int row, const SqlDateTime& datetime)
{
	if (_data_type!=OCI_TYPECODE_TIME && _data_type!=OCI_TYPECODE_TIMESTAMP)
		THROW_OCI_EXCEPTION("invalid variant data type");

	_psql_date->set_datetime(row, datetime);
}


bool SqlVariantArray::is_not_null(int row) const
{
	switch(_data_type) {
	  case 0:
#ifdef SQLT_BFLOAT
	  case OCI_TYPECODE_BFLOAT:
	  case OCI_TYPECODE_BDOUBLE:
#endif
		return true;

	  case OCI_TYPECODE_VARCHAR:
		return _pstring->is_not_null(row);

	  case OCI_TYPECODE_NUMBER:
		return _pnumber->is_not_null(row);

	  case OCI_TYPECODE_DATE:
		return _poci_date->is_not_null(row);

#if ORACLE_VERSION>=81
	  case OCI_TYPECODE_TIME:
	  case OCI_TYPECODE_TIMESTAMP:
		return _psql_date->is_not_null(row);
#endif
/*
	  case OCI_TYPECODE_BLOB:
		return _pblob->is_not_null(row);

	  case OCI_TYPECODE_CLOB:
		return _pclob->is_not_null(row);
*/
	  case SQLT_LNG:
		return _pstring->is_not_null(row);	//@@

	  default:
		assert(0);
		return true;
	}
}

void SqlVariantArray::bind_result(SqlStatement& stmt, int idx)
{
	switch(_data_type) {
	  case OCI_TYPECODE_VARCHAR:
		stmt.bind_result(idx, *_pstring);
		break;

	  case OCI_TYPECODE_NUMBER:
		stmt.bind_result(idx, *_pnumber);
		break;

#ifdef SQLT_BFLOAT
	  case OCI_TYPECODE_BFLOAT:
		stmt.bind_result(idx, *_pfloat);
		break;

	  case OCI_TYPECODE_BDOUBLE:
		stmt.bind_result(idx, *_pdouble);
		break;
#endif

	  case OCI_TYPECODE_DATE:
		stmt.bind_result(idx, *_poci_date);
		break;

#if ORACLE_VERSION>=81
	  case OCI_TYPECODE_TIME:
	  case OCI_TYPECODE_TIMESTAMP:
		stmt.bind_result(idx, *_psql_date);
		break;
#endif
/*
	  case OCI_TYPECODE_BLOB:
		stmt.bind_result(idx, *_pblob);
		break;

	  case OCI_TYPECODE_CLOB:
		stmt.bind_result(idx, *_pclob);
		break;
*/
	  case SQLT_LNG:
		stmt.bind_result(idx, *_pstring);	//@@
		break;

	  default:
		assert(0);
	}
}

 // buffer bind values
void SqlVariantArray::bind_input(SqlStatement& stmt, int idx, int row)
{
	switch(_data_type) {
	  case OCI_TYPECODE_VARCHAR:
		stmt.bind_input(idx, BindInput(_pstring->str(row), _pstring->alen(), SQLT_STR));
		break;

	  case OCI_TYPECODE_NUMBER:
		stmt.bind_input(idx, BindInput(_pnumber->get_number(row), _pnumber->ind(row)));
		break;

#ifdef SQLT_BFLOAT
	  case OCI_TYPECODE_BFLOAT:
		stmt.bind_input(idx, *_pfloat);
		break;

	  case OCI_TYPECODE_BDOUBLE:
		stmt.bind_input(idx, *_pdouble);
		break;
#endif

	  case OCI_TYPECODE_DATE:
		stmt.bind_input(idx, BindInput(_poci_date->get_date(row), _poci_date->ind(row)));
		break;

#if ORACLE_VERSION>=81
	  case OCI_TYPECODE_TIME:
	  case OCI_TYPECODE_TIMESTAMP:
		stmt.bind_input(idx, BindInput(_psql_date->get_datetime(row), _psql_date->ind(row)));
		break;
#endif
/*
	  case OCI_TYPECODE_BLOB:
		stmt.bind_input(idx, *_pblob);
		break;

	  case OCI_TYPECODE_CLOB:
		stmt.bind_input(idx, *_pclob);
		break;
*/
	  case SQLT_LNG:
		stmt.bind_input(idx, BindInput(_pstring->str(row), _pstring->alen(), SQLT_STR));	//@@
		break;

	  default:
		assert(0);
	}
}

/*
void SqlStringArray::assign(int row, const dvoid* ptr, sb4 size)
{
	//TODO resize buffer as needed
	if (value_sz == -1)
		strcpy_s(_buffer, _blen, s);
	else {
		memcpy(_buffer, ptr, size);
		_buffer[size] = '\0';
	}
} */

const TCHAR* get_oci_ptype_str(ub1 type)
{
	switch(type) {
	  case OCI_PTYPE_TABLE:			return TEXT("table");
	  case OCI_PTYPE_VIEW:			return TEXT("view");
	  case OCI_PTYPE_PROC:			return TEXT("procedure");
	  case OCI_PTYPE_FUNC:			return TEXT("function");
	  case OCI_PTYPE_PKG:			return TEXT("package");
	  case OCI_PTYPE_TYPE:			return TEXT("user-defined type");
	  case OCI_PTYPE_SYN:			return TEXT("synonym");
	  case OCI_PTYPE_SEQ:			return TEXT("sequence");
	  case OCI_PTYPE_COL:			return TEXT("column");
	  case OCI_PTYPE_ARG:			return TEXT("argument");
	  case OCI_PTYPE_LIST:			return TEXT("list");
	  case OCI_PTYPE_TYPE_ATTR:		return TEXT("user-defined type's attribute");
	  case OCI_PTYPE_TYPE_COLL:		return TEXT("collection type's element ");
	  case OCI_PTYPE_TYPE_METHOD:	return TEXT("user-defined type's method");
	  case OCI_PTYPE_TYPE_ARG:		return TEXT("user-defined type method's argument");
	  case OCI_PTYPE_TYPE_RESULT:	return TEXT("user-defined type method's result");
#if ORACLE_VERSION>=81
	  case OCI_PTYPE_SCHEMA:		return TEXT("schema");
	  case OCI_PTYPE_DATABASE:		return TEXT("database");
#endif
	}

	return TEXT("unknown type");
}


static tstring tokenize(const TCHAR** ptr, const TCHAR* sep)
{
	const TCHAR* start = *ptr;
	const TCHAR* end;

	const TCHAR* found = _tcspbrk(*ptr, sep);

	if (found) {
		end = found;
		*ptr = found;
	} else
		*ptr = end = start + _tcslen(start);

	return tstring(start, end-start);
}

static tstring tokenize(const TCHAR** ptr, const TCHAR* sep, TCHAR key)
{
	if (**ptr != key)
		return TEXT("");

	++*ptr;

	return tokenize(ptr, sep);
}

void LoginPara::parse(const TCHAR* conn_str)
{
	const TCHAR* ptr = conn_str;

	_username = tokenize(&ptr, TEXT("/@ "));
	_password = tokenize(&ptr, TEXT("@ "), TEXT('/'));
	_tnsname = tokenize(&ptr, TEXT(" "), TEXT('@'));

	while(istspace(*ptr))
		++ptr;

	if (!_tcsnicmp(ptr, TEXT("as sysdba"), 9)) {
		_mode = OCI_SYSDBA;
		ptr += 9;
	} else if (!_tcsnicmp(ptr, TEXT("as sysoper"), 10)) {
		_mode = OCI_SYSDBA;
		ptr += 10;
	} else if (!_tcsnicmp(ptr, TEXT("migrate"), 7)) {
		_mode = OCI_MIGRATE;
		ptr += 7;
	}
}


} // namespace OCIPL
