
 //
 // mysqlpl.cpp
 //
 // Copyright (c) 2004, 2006, 2007, 2009 Martin Fuchs <martin-fuchs@gmx.net>
 //

/*

  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 _NO_COMMENT
#define _NO_COMMENT	// no #pragma comment(lib, ...) statements in .lib files
#endif

#include "mysqlpl.h"

#include <sstream>
#include <assert.h>


#ifndef _MSC_VER

#define _snprintf snprintf

inline void strupr(char* s)
{
	for(; *s; ++s)
		*s = toupper(*s);
}

#endif


namespace MySqlPL {


MSqlException::MSqlException(MYSQL* db, const char* file, int line)
{
	char buffer[2048];

	_sql_error_code = mysql_errno(db);
	const char* error_str = mysql_error(db);

#if MYSQLPL_PREP_STMT
	const char* state_str = mysql_sqlstate(db);
	sprintf(buffer, "MySQL error %d - SQLSTATE %s: %s", _sql_error_code, state_str, error_str);
	_sqlstate = state_str;
#else
	sprintf(buffer, "MySQL error %d: %s", _sql_error_code, error_str);
#endif

	_msg = strdup((const char*)buffer);

	_file = file;
	_line = line;
}

#if MYSQLPL_PREP_STMT
MSqlException::MSqlException(MYSQL_STMT* s, const char* file, int line)
{
	char buffer[2048];

	_sql_error_code = mysql_stmt_errno(s);
	const char* error_str = mysql_stmt_error(s);

	const char* state_str = mysql_stmt_sqlstate(s);
	sprintf(buffer, "MySQL error %d - SQLSTATE %s: %s", _sql_error_code, state_str, error_str);
	_sqlstate = state_str;

	_msg = strdup(buffer);

	_file = file;
	_line = line;
}

MSqlException::MSqlException(MYSQL_STMT* s, SqlStatement& stmt, const char* file, int line)
{
	char buffer[2048];

	_sql_error_code = mysql_stmt_errno(s);
	const char* error_str = mysql_stmt_error(s);
	const char* state_str = mysql_stmt_sqlstate(s);

	sprintf(buffer, "MySQL error %d - SQLSTATE %s: %s", _sql_error_code, state_str, error_str);

	_last_sql = stmt.get_last_sql();

	if (!_last_sql.empty()) {
		const char* sql = _last_sql.c_str();

		_snprintf((char*)buffer, sizeof(buffer)-1, "%s\nlast SQL statement:\n%s\n", buffer, sql);
	}

	_msg = strdup((const char*)buffer);
	_sqlstate = state_str;

	_file = file;
	_line = line;
}
#endif

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


void MSqlConnection::connect(const char* username, const char* password, const char* db_name,
				const char* host, unsigned port, unsigned long clientflag)
{
	_connected = false;

	_db = mysql_real_connect(_env, host, username, password, db_name, port, NULL, clientflag);

	if (!_db)
		THROW_MYSQLPL_EXCEPTION(_env);

	_connected = true;

#if MYSQLPL_PREP_STMT
	 // disable auto commit mode
	my_bool res = mysql_autocommit(_db, 0);
	mysqlpl_check_error(_env, res);
#else
	SqlStatement(*this).execute("set autocommit=0");	// "set autocommit=0" is not supported before MySQL 4.1
#endif
}


#if MYSQLPL_PREP_STMT
unsigned MSqlConnection::get_warnings_count() const
{
	unsigned res = mysql_warning_count(_db);

	if (res == -1)
		THROW_MYSQLPL_EXCEPTION(_db);

	return res;
}

std::string MSqlConnection::get_info() const
{
	const char* info = mysql_info(_db);

	if (!info)
		THROW_MYSQLPL_EXCEPTION(_db);

	return info;
}
#else
const char* MSqlConnection::get_info() const
{
	return mysql_info(_db);
}
#endif


#if MYSQLPL_PREP_STMT

std::string SqlInt::str() const
{
	if (_ind.is_null())
		return "";

	char buffer[16];

	sprintf(buffer, "%d", _value);

	return buffer;
}

std::string SqlInteger::str() const
{
	if (_ind.is_null())
		return "";

	char buffer[32];

	sprintf(buffer, "%I64d", _value);

	return buffer;
}

SqlNumber::SqlNumber(const char* s)
{
	if (sscanf(s, "%lf", &_value) == 1)	// %lf -> double
		_ind.set();
}

std::string SqlNumber::str() const
{
	if (_ind.is_null())
		return "";

	char buffer[64];

	sprintf(buffer, "%f", _value);	// %f -> double

	return buffer;
}


SqlDate::SqlDate(const char* str, enum_mysql_timestamp_type type)
{
	while(str && isspace(*str))
		++str;

	if (str && *str) {
		hour = 0;
		minute = 0;
		second = 0;
		second_part = 0;

		switch(type) {
		  case MYSQL_TIMESTAMP_DATE:
			if (sscanf(str, "%04d-%02d-%02d",
							&year, &month, &day) != 3)
				if (sscanf(str, "%02d.%02d.%04d",
								&day, &month, &year) != 3)
					THROW_MYSQLPL_EXCEPTION("date conversion error");
			break;

		  case MYSQL_TIMESTAMP_TIME:
			if (sscanf(str, "%02d:%02d:%02d.%06d",
							&hour, &minute, &second, &second_part) < 3)
				THROW_MYSQLPL_EXCEPTION("time conversion error");
			break;

		  //case MYSQL_TIMESTAMP_DATETIME:
		  default:
			if (sscanf(str, "%04d-%02d-%02d %02d:%02d:%02d.%06d",
							&year, &month, &day, &hour, &minute, &second, &second_part) < 6) {
				second = 0;
				if (sscanf(str, "%02d.%02d.%04d %02d:%02d:%02d.%06d",
								&day, &month, &year, &hour, &minute, &second, &second_part) < 3)
					THROW_MYSQLPL_EXCEPTION("datetime conversion error");
			}
		}

		super::time_type = type;

		_ind.set();
	} else {
		super::time_type = MYSQL_TIMESTAMP_NONE;

		_ind.clear();
	}
}

SqlDate::SqlDate(const SqlDate& other, enum_mysql_timestamp_type type)
 :	SqlValue(other)
{
	memcpy(this, &other, sizeof(MYSQL_TIME));

	if (other.time_type != type) {
		if (type==MYSQL_TIMESTAMP_NONE || type==MYSQL_TIMESTAMP_ERROR)
			super::time_type = type;
		else {
			if (other.time_type == MYSQL_TIMESTAMP_DATE) {
				if (type == MYSQL_TIMESTAMP_TIME)
					THROW_MYSQLPL_EXCEPTION("no conversion from time to date");

				super::hour = 0;
				super::minute = 0;
				super::second = 0;
			} else if (other.time_type == MYSQL_TIMESTAMP_TIME) {
				if (type == MYSQL_TIMESTAMP_DATE)
					THROW_MYSQLPL_EXCEPTION("no conversion from date to time");

				super::year = 0;
				super::month = 0;
				super::day = 0;
			}

			super::time_type = type;
		}
	}
}


std::string SqlDate::str() const
{
	if (_ind.is_not_null()) {
		char buffer[40];

		switch(time_type) {
		  case MYSQL_TIMESTAMP_DATE:
			sprintf(buffer, "%04d-%02d-%02d",
								year, month, day);
			break;

		  case MYSQL_TIMESTAMP_TIME:
			sprintf(buffer, "%02d:%02d:%02d.%06d",
								hour, minute, second, second_part);
			break;

		  //case MYSQL_TIMESTAMP_DATETIME:
		  default:
			sprintf(buffer, "%04d-%02d-%02d %02d:%02d:%02d.%06d",
								year, month, day, hour, minute, second, second_part);
		}

		return buffer;
	} else
		return std::string();
}


std::string MSqlBlob::str() const
{
	if (_ind.is_not_null()) {
		return std::string((char*)_ptr, _len);
	} else
		return std::string();
}

#endif // MYSQLPL_PREP_STMT


std::string ColumnType::get_type_str(bool show_null) const
{
	std::ostringstream str;

	switch(_data_type) {
	  case MYSQL_TYPE_STRING:
		str << "char(" << _width << ")";
		break;

	  case MYSQL_TYPE_VAR_STRING:
		str << "varchar(" << _width << ")";
		break;

	  case MYSQL_TYPE_DECIMAL:
		if (_width == 0)
			str << "decimal";
		else
			str << "decimal(" << (_width-1) << "," << _decimals << ")";
		break;

#if MYSQL_VERSION_ID>=50000
	  case MYSQL_TYPE_NEWDECIMAL:
		if (_width == 0)
			str << "newdecimal";
		else
			str << "newdecimal(" << (_width-1) << "," << _decimals << ")";
		break;
#endif

	  case MYSQL_TYPE_TINY:
		str << "tinyint(" << _width << ")";
		break;
	  case MYSQL_TYPE_SHORT:
		str << "shortint(" << _width << ")";
		break;
	  case MYSQL_TYPE_LONG:
		str << "int(" << _width << ")";
		break;
	  case MYSQL_TYPE_LONGLONG:
		str << "bigint(" << _width << ")";
		break;

#ifdef FIELD_TYPE_ENUM
	  case FIELD_TYPE_ENUM:
		str << "enum";
		break;
#endif

	  case MYSQL_TYPE_FLOAT:
		if (_width == 12)
			str << "float";
		else
			str << "float(" << _width << "," << _decimals << ")";
		break;

	  case MYSQL_TYPE_DOUBLE:
		if (_width == 22)
			str << "double";
		else
			str << "double(" << _width << "," << _decimals << ")";
		break;

	  case MYSQL_TYPE_DATE:
		str << "date";
		break;
#if MYSQL_VERSION_ID>=50000
	  case MYSQL_TYPE_NEWDATE:
		str << "newdate";
		break;
#endif
	  case MYSQL_TYPE_DATETIME:
		str << "datetime";
		break;
	  case MYSQL_TYPE_TIME:
		str << "time";
		break;
	  case MYSQL_TYPE_YEAR:
		str << "year(" << _width << ")";
		break;

	  case MYSQL_TYPE_TINY_BLOB:
		if (_flags & BINARY_FLAG)
			str << "tinyblob";
		else
			str << "tinytext";
		break;
	  case MYSQL_TYPE_MEDIUM_BLOB:
		if (_flags & BINARY_FLAG)
			str << "mediumblob";
		else
			str << "mediumtext";
		break;
	  case MYSQL_TYPE_LONG_BLOB:
		if (_flags & BINARY_FLAG)
			str << "longblob";
		else
			str << "longtext";
		break;
	  case MYSQL_TYPE_BLOB:
		if (_flags & BINARY_FLAG)
			str << "blob";
		else
			str << "text";
		break;

	  default:
		str << "unknown";
	}

	if (show_null)
		if (IS_NOT_NULL(_flags))
			str << " not null";
		else
			str << " null";

	return str.str();
}


SqlStatement::SqlStatement(MSqlConnection& conn)
 :	_db(conn),
	_state(UNINITIALIZED),
	_res(NULL),
	_stmt_type(-1),
	_result(NULL)
{
#if MYSQLPL_PREP_STMT
	_stmt = mysql_stmt_init(_db);
#endif
}

SqlStatement::SqlStatement(MSqlConnection& conn, const std::string& sql)
 :	_db(conn),
	_state(UNINITIALIZED),
	_res(NULL),
	_stmt_type(-1),
	_result(NULL)
{
#if MYSQLPL_PREP_STMT
	_stmt = mysql_stmt_init(_db);
#endif

	prepare(sql);
}

SqlStatement::SqlStatement(MSqlConnection& conn, const SqlQuery& query)
 :	_db(conn),
	_state(UNINITIALIZED),
	_res(NULL),
	_stmt_type(-1),
	_result(NULL)
{
#if MYSQLPL_PREP_STMT
	_stmt = mysql_stmt_init(_db);
#endif

	execute(query);
}

SqlStatement::~SqlStatement()
{
#if MYSQLPL_PREP_STMT
	if (_stmt)
		mysql_stmt_close(_stmt);
#else
	if (_result)
		mysql_free_result(_result);
#endif
}


void SqlStatement::prepare(const std::string& sql)
{
	delete _res;
	_res = NULL;

	_last_sql = sql;

#if MYSQLPL_PREP_STMT
	int res = mysql_stmt_prepare(_stmt, sql.c_str(), sql.length());
	mysqlpl_check_error(_stmt, res);

	_state = PREPARED;
#else
	_sql = sql;

	if (_result)
		mysql_free_result(_result);
#endif

	_stmt_type = -1;
	_result = NULL;
//	_result_bind_vector.clear();
	_param_bind_vector.clear();
}


my_ulonglong SqlStatement::get_row_count() const
{
#if MYSQLPL_PREP_STMT
	if (is_select()) {
		if (mysql_stmt_store_result(_stmt))
			THROW_MYSQLPL_EXCEPTION(_stmt);
	}

	my_ulonglong res = mysql_stmt_affected_rows(_stmt);	// for DML statements
/*
	if (res == (my_ulonglong)-1)
		res = mysql_stmt_num_rows(_stmt);	// only for SELECT statements
*/

	if (res == (my_ulonglong)-1)
		THROW_MYSQLPL_EXCEPTION(_stmt);
#else
	int res = mysql_affected_rows(_db);

	if (res == -1)
		THROW_MYSQLPL_EXCEPTION(_db);
#endif

	return res;
}


#if MYSQLPL_PREP_STMT//@@
void SqlStatement::bind_result(MYSQL_BIND* bind_array)
{
	my_bool res = mysql_stmt_bind_result(_stmt, bind_array);

	mysqlpl_check_error(_stmt, res);

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

		_state = (_state|RESULT_BOUND) & ~FETCHED;
	} else
		_state |= RESULT_BOUND;
}
#endif


void SqlStatement::execute()
{
#if MYSQLPL_PREP_STMT
	if (!(_state & RESULT_BOUND)) {
		 // a SELECT statement without ready define variables?
		if (is_select()) {	// a statement with result set? (=> SELECT)
			_result_bind_vector.resize(get_column_count());

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

	MSqlBindVector param_mbind_vector;

	 // bind parameter values
	if (!_param_bind_vector.empty()) {
		 // Umwandlung der Bindparameter von BindPar in MYSQL_BIND-Strukturelemente
		param_mbind_vector.resize(_param_bind_vector.size());

		MSqlBindVector::iterator out = param_mbind_vector.begin();
		for(BindVector::const_iterator in=_param_bind_vector.begin(); in!=_param_bind_vector.end(); ++in,++out)
			mysql_bind_param(*in, *out);

		my_bool res = mysql_stmt_bind_param(_stmt, &param_mbind_vector[0]);

		mysqlpl_check_error(_stmt, res);
	}

	int res = mysql_stmt_execute(_stmt);

	mysqlpl_check_error(_stmt, res);
#else
	int res = mysql_query(_db, create_bound_sql().c_str());

	mysqlpl_check_error(_db, res);

	if (_result)
		mysql_free_result(_result);

	_result = mysql_store_result(_db);

	if (_result) { // is_select()
		 // define resultset variables
		if (_res)
			delete _res;

		_res = new SqlResult(*this);
	}
#endif

	_state = (_state|EXECUTED) & ~FETCHED;
}

#if !MYSQLPL_PREP_STMT
static const char* next_bind_variable(const char* p)
{
	for(; *p; ++p)
		if (*p == '?')
			return p;
		 // look for quotes and escape characters
		else if (*p == '\'') {
			while(*++p)
				if (*p=='\'' && *++p!='\'')
					break;
		}

	return NULL;
}

 // create sql statement from prepared statement and integrate bind variables
std::string SqlStatement::create_bound_sql() const
{
	const char* p = _sql.c_str();
	std::string sql;
	char buffer[64];

	for(BindVector::const_iterator it=_param_bind_vector.begin(); it!=_param_bind_vector.end(); ++it) {
		const char* q = next_bind_variable(p);

		if (!q)
			THROW_MYSQLPL_EXCEPTION("too much bind values");

		sql.append(p, q-p);

		const BindPar& value = *it;

		switch(value._type) {
		  case MYSQL_TYPE_VAR_STRING: {
			sql.append("'");

			 // escape quote characters in bound string constants
			const char* p = (const char*)value._ptr;

			for(int i=0; i<value._len; ++i,++p)
				if (*p == '\'')
					sql.append("''");
				else
					sql.append(p, 1);

			sql.append("'");
			break;}

		  case MYSQL_TYPE_LONG:
			sql.append(buffer, sprintf(buffer,"%d",*(int*)value._ptr));
			break;

		  case MYSQL_TYPE_DOUBLE:
			sql.append(buffer, sprintf(buffer,"%f",*(double*)value._ptr));
			break;

		  default:
			THROW_MYSQLPL_EXCEPTION("unsupported datatype");
		}

		p = q + 1;
	}

	if (next_bind_variable(p))
		THROW_MYSQLPL_EXCEPTION("too less bind values");

	sql.append(p);

	return sql;
}	 
#endif


MYSQL_RES* SqlStatement::get_meta_res() const
{
#if MYSQLPL_PREP_STMT
	if (!_result) {
		_result = mysql_stmt_result_metadata(_stmt);

		if (!_result)
			THROW_MYSQLPL_EXCEPTION("no result set metadata");
	}
#endif

	return _result;
}

bool SqlStatement::is_select() const
{
#if MYSQLPL_PREP_STMT
	if (!_result)
		_result = mysql_stmt_result_metadata(_stmt);

	return _result!=NULL;
#else
	if (!(_state & EXECUTED))
		THROW_MYSQLPL_EXCEPTION("statement not yet executed");

	return _result!=NULL;
#endif
}


int SqlStatement::get_column_count() const
{
#if MYSQLPL_PREP_STMT
	unsigned count = mysql_num_fields(get_meta_res());

	if (count == (unsigned)-1)
		THROW_MYSQLPL_EXCEPTION(_stmt);

	return count;
#else
	return mysql_num_fields(_result);
#endif
}

void SqlStatement::get_column_type(int idx, ColumnType& info) const
{
	MYSQL_FIELD* field = mysql_fetch_field_direct(get_meta_res(), idx);

	if (!field)
#if MYSQLPL_PREP_STMT
		THROW_MYSQLPL_EXCEPTION(_stmt);
#else
		THROW_MYSQLPL_EXCEPTION(_db);
#endif

	info.init(field);
}

void ColumnType::init(const MYSQL_FIELD* field)
{
	 // get column name
	_table_name = field->table;
	_name = field->name;

	 // get column data type
	_data_type = field->type;

	_width = field->length;
	_decimals = field->decimals;

	 // get NULL-ability, binary, ... flags
	_flags = field->flags;
}


void SqlStatement::bind_param(int idx, BindPar par)
{
	if (_param_bind_vector.size() <= idx)
		_param_bind_vector.resize(idx+1);

	_param_bind_vector.bind(idx, par);
}


void SqlQuery::bind_param(BindPar par)
{
	int idx = _param_bind_vector.size();

	_param_bind_vector.resize(idx+1);

	_param_bind_vector.bind(idx, par);
}


void BindVector::bind(int idx, const BindPar& par)
{
	at(idx) = par;
}


void SqlStatement::print_results(std::ostream& out, bool header)
{
	if (!(_state & EXECUTED))
		execute();

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

	int row_cnt = 0;

	while(fetch()) {
		++row_cnt;

		out << "row " << row_cnt << ":\n";

		_res->print_values(out);

		out << std::endl;
	}
}


SqlRecord::SqlRecord(SqlStatement& stmt)
 :	_stmt(stmt)
{
	int column_cnt = stmt.get_column_count();

	_columns.resize(column_cnt);

//	unsigned num_fields = mysql_num_fields(stmt.get_meta_res());
	MYSQL_FIELD* fields = mysql_fetch_fields(stmt.get_meta_res());

	for(ColumnVector::iterator it=_columns.begin(); it!=_columns.end(); ++it) {
		it->_type.init(fields++);

#if MYSQLPL_PREP_STMT
		it->_column = SqlVariant(it->_type._data_type);
#endif
	}

	//_name_map.clear();
}

const ColumnType& SqlRecord::get_column_type(const std::string& col_name) const
{
	NameMap::const_iterator found = find_column(col_name);

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

#if MYSQLPL_PREP_STMT
const SqlVariant& SqlRecord::get_column(const std::string& col_name) const
{
	NameMap::const_iterator found = find_column(col_name);

	if (found != _name_map.end()) {
		int idx = found->second;
		return _columns[idx]._column;
	} else {
		THROW_MYSQLPL_EXCEPTION("column not found");
		return *(SqlVariant*)NULL;
	}
}
#endif

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

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

		for(ColumnVector::const_iterator it=_columns.begin(); it!=_columns.end(); ++it,++idx) {
			upper_name = it->_type._name.c_str();
			strupr((char*)upper_name.data());

			_name_map[upper_name] = idx;
		}
	}

	upper_name = col_name;
	strupr((char*)upper_name.data());

	return _name_map.find(upper_name);
}

int SqlRecord::get_column_idx(const std::string& col_name) const
{
	NameMap::const_iterator found = find_column(col_name);

	if (found != _name_map.end())
		return found->second;
	else {
		THROW_MYSQLPL_EXCEPTION("column not found");
		return -1;
	}
}


void SqlRecord::print_columns(std::ostream& out)
{
	for(ColumnVector::const_iterator it=_columns.begin(); it!=_columns.end(); ++it) {
		const ColumnType& type = it->_type;

		if (!type._table_name.empty())
			out << type._table_name << ".";

		out << type._name << ": " << type.get_type_str() << std::endl;
	}

	out << std::endl;
}

void SqlRecord::print_values(std::ostream& out)
{
#if MYSQLPL_PREP_STMT
	for(ColumnVector::const_iterator it=_columns.begin(); it!=_columns.end(); ++it) {
		const ColumnType& type = it->_type;

//		if (!type._table_name.empty())
//			out << type._table_name << ".";

		out << type._name << "=" << it->_column.str() << std::endl;
	}
#else
	for(size_t i=0; i<_columns.size(); ++i) {
		const ColumnType& type = _columns[i]._type;

//		if (!type._table_name.empty())
//			out << type._table_name << ".";

		out << type._name << "=" << _stmt->get_string(i) << std::endl;
	}
#endif
}


#if MYSQLPL_PREP_STMT

SqlVariant::SqlVariant(enum_field_types data_type)
{
	_data_type = data_type;

	switch(data_type) {
	  case MYSQL_TYPE_STRING:
		_data_type = MYSQL_TYPE_VAR_STRING;
		// fall through

	  case MYSQL_TYPE_VAR_STRING:
		_string = new SqlString;
		break;

	  case MYSQL_TYPE_TINY:
	  case MYSQL_TYPE_SHORT:
	  case MYSQL_TYPE_INT24:
	  case MYSQL_TYPE_YEAR:
#ifdef FIELD_TYPE_ENUM
	  case FIELD_TYPE_ENUM:
#endif
		_data_type = MYSQL_TYPE_LONG;
		// fall through
	  case MYSQL_TYPE_LONG:
		_int = new SqlInt;
		break;

	  case MYSQL_TYPE_LONGLONG:
		_integer = new SqlInteger;
		break;

	  case MYSQL_TYPE_DECIMAL:
#if MYSQL_VERSION_ID>=50000
	  case MYSQL_TYPE_NEWDECIMAL:
#endif
	  case MYSQL_TYPE_FLOAT:
		_data_type = MYSQL_TYPE_DOUBLE;
		// fall through

	  case MYSQL_TYPE_DOUBLE:
		_number = new SqlNumber;
		break;

	  case MYSQL_TYPE_DATE:
		_date = new SqlDate(MYSQL_TIMESTAMP_DATE);
		break;

#if MYSQL_VERSION_ID>=50000
	  case MYSQL_TYPE_NEWDATE:
		_date = new SqlDate(MYSQL_TIMESTAMP_DATE);
		break;
#endif

	  case MYSQL_TYPE_DATETIME:
		_date = new SqlDate(MYSQL_TIMESTAMP_DATETIME);
		break;

	  case MYSQL_TYPE_TIMESTAMP:
		_date = new SqlDate(MYSQL_TIMESTAMP_TIME);
		break;

	  case MYSQL_TYPE_TINY_BLOB:
	  case MYSQL_TYPE_MEDIUM_BLOB:
	  case MYSQL_TYPE_LONG_BLOB:
		_data_type = MYSQL_TYPE_BLOB;
		// fall through
	  case MYSQL_TYPE_BLOB:
		_blob = new MSqlBlob;
		break;

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

SqlVariant::SqlVariant(const SqlVariant& other)
{
	_data_type = (enum_field_types)-1;

	assign(other);
}

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

	_data_type = other._data_type;

	switch(other._data_type) {
	  case -1:
		break;

	  case MYSQL_TYPE_VAR_STRING:
		_string = new SqlString(*other._string);
		break;

	  case MYSQL_TYPE_LONG:
		_int = new SqlInt(*other._int);
		break;

	  case MYSQL_TYPE_LONGLONG:
		_integer = new SqlInteger(*other._integer);
		break;

	  case MYSQL_TYPE_DOUBLE:
		_number = new SqlNumber(*other._number);
		break;

	  case MYSQL_TYPE_DATE:
#if MYSQL_VERSION_ID>=50000
	  case MYSQL_TYPE_NEWDATE:
#endif
	  case MYSQL_TYPE_DATETIME:
	  case MYSQL_TYPE_TIME:
	  case MYSQL_TYPE_TIMESTAMP:
		_date = new SqlDate(*other._date);
		break;

	  case MYSQL_TYPE_BLOB:
		_blob = new MSqlBlob(*other._blob);
		break;

	  default:
		assert(0);
	}
}

void SqlVariant::clear()
{
	switch(_data_type) {
	  case -1:
		break;

	  case MYSQL_TYPE_VAR_STRING:
		delete _string;
		break;

	  case MYSQL_TYPE_LONG:
		delete _int;
		break;

	  case MYSQL_TYPE_LONGLONG:
		delete _integer;
		break;

	  case MYSQL_TYPE_DOUBLE:
		delete _number;
		break;

	  case MYSQL_TYPE_DATE:
#if MYSQL_VERSION_ID>=50000
	  case MYSQL_TYPE_NEWDATE:
#endif
	  case MYSQL_TYPE_DATETIME:
	  case MYSQL_TYPE_TIME:
	  case MYSQL_TYPE_TIMESTAMP:
		delete _date;
		break;

	  case MYSQL_TYPE_BLOB:
		delete _blob;
		break;

	  default:
		assert(0);
	}

	_data_type = (enum_field_types)-1;
}

std::string SqlVariant::str() const
{
	switch(_data_type) {
	  case -1:
		return "";

	  case MYSQL_TYPE_VAR_STRING:
		return _string->c_str();
//		return *_string;

	  case MYSQL_TYPE_LONG:
		return _int->str();

	  case MYSQL_TYPE_LONGLONG:
		return _integer->str();

	  case MYSQL_TYPE_DOUBLE:
		return _number->str();

	  case MYSQL_TYPE_DATE:
#if MYSQL_VERSION_ID>=50000
	  case MYSQL_TYPE_NEWDATE:
#endif
	  case MYSQL_TYPE_DATETIME:
	  case MYSQL_TYPE_TIME:
	  case MYSQL_TYPE_TIMESTAMP:
		return _date->str();

	  case MYSQL_TYPE_BLOB:
		return _blob->str();
		break;

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

SqlNumber SqlVariant::number() const
{
	switch(_data_type) {
	  case -1:
		return SqlNumber();

	  case MYSQL_TYPE_VAR_STRING:
		return SqlNumber(*_string);

	  case MYSQL_TYPE_LONG:
		return (int)*_int;

	  case MYSQL_TYPE_LONGLONG:
		return (LONGLONG)*_integer;

	  case MYSQL_TYPE_DOUBLE:
		return *_number;

	//case MYSQL_TYPE_DATETIME:
	// 	return ...

	  default:
		THROW_MYSQLPL_EXCEPTION("no conversion to number");
	}
}

int SqlVariant::get_int() const
{
	switch(_data_type) {
	  case -1:
		return 0;

	  case MYSQL_TYPE_VAR_STRING:
		return atoi(*_string);

	  case MYSQL_TYPE_LONG:
		return *_int;

	  case MYSQL_TYPE_LONGLONG:
		return *_integer;

	  case MYSQL_TYPE_DOUBLE:
		return _number->get_int();

	//case MYSQL_TYPE_DATETIME:
	// 	return ...

	  default:
		THROW_MYSQLPL_EXCEPTION("no conversion to int");
	}
}

#ifdef _MSC_VER
__int64 SqlVariant::get_int64() const
{
	switch(_data_type) {
	  case -1:
		return 0;

	  case MYSQL_TYPE_VAR_STRING:
		return _atoi64(*_string);

	  case MYSQL_TYPE_LONG:
		return *_int;

	  case MYSQL_TYPE_LONGLONG:
		return *_integer;

	  case MYSQL_TYPE_DOUBLE:
		return _number->get_int64();

	//case MYSQL_TYPE_DATETIME:
	// 	return ...

	  default:
		THROW_MYSQLPL_EXCEPTION("no conversion to int64");
	}
}
#endif

double SqlVariant::get_double() const
{
	switch(_data_type) {
	  case -1:
		return 0;

	  case MYSQL_TYPE_VAR_STRING:
		return atof(*_string);

	  case MYSQL_TYPE_LONG:
		return *_int;

	  case MYSQL_TYPE_LONGLONG:
		return *_integer;

	  case MYSQL_TYPE_DOUBLE:
		return _number->get_double();

	//case MYSQL_TYPE_DATETIME:
	// 	return ...

	  default:
		THROW_MYSQLPL_EXCEPTION("no conversion to double");
	}
}

SqlDate SqlVariant::date(enum_mysql_timestamp_type type) const
{
	switch(_data_type) {
	  case -1:
		return SqlDate();

	  case MYSQL_TYPE_VAR_STRING:
		return SqlDate(*_string, MYSQL_TIMESTAMP_DATE);

	  case MYSQL_TYPE_DATE:
#if MYSQL_VERSION_ID>=50000
	  case MYSQL_TYPE_NEWDATE:
#endif
	  case MYSQL_TYPE_DATETIME:
	  case MYSQL_TYPE_TIME:
	  case MYSQL_TYPE_TIMESTAMP:
	 	return SqlDate(*_date, type);

	  default:
		THROW_MYSQLPL_EXCEPTION("no conversion to date type");
	}
}

bool SqlVariant::is_null() const
{
	switch(_data_type) {
	  case -1:
		return true;

	  case MYSQL_TYPE_VAR_STRING:
		return _string->is_null();

	  case MYSQL_TYPE_LONG:
		return _int->is_null();

	  case MYSQL_TYPE_LONGLONG:
		return _integer->is_null();

	  case MYSQL_TYPE_DOUBLE:
		return _number->is_null();

	  case MYSQL_TYPE_DATE:
#if MYSQL_VERSION_ID>=50000
	  case MYSQL_TYPE_NEWDATE:
#endif
	  case MYSQL_TYPE_DATETIME:
	  case MYSQL_TYPE_TIME:
	  case MYSQL_TYPE_TIMESTAMP:
		return _date->is_null();

	  case MYSQL_TYPE_BLOB:
		return _blob->is_null();

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

void mysql_bind_param(const BindPar& par, MYSQL_BIND& bind)
{
	memset(&bind, 0, sizeof(bind));

	bind.buffer = par._ptr;
	bind.buffer_type = par._type;
	bind.buffer_length = par._len;
	bind.is_null = par._pind;
	bind.length = par._plen;
}

void SqlStatement::bind_result(ColumnVector& columns)
{
	int idx = 0;

	for(ColumnVector::iterator it=columns.begin(); it!=columns.end(); ++it)
		it->_column.bind_result_column(*this, _result_bind_vector[idx++]);

	bind_result(&_result_bind_vector[0]);
}

void SqlStatement::bind_result_column(BindResultPar par, MYSQL_BIND& bind)
{
	memset(&bind, 0, sizeof(bind));

	bind.buffer = par._ptr;
	bind.buffer_type = par._type;
	bind.buffer_length = par._len;
	bind.is_null = par._pind;
	bind.length = par._plen;
}

void SqlVariant::bind_result_column(SqlStatement& stmt, MYSQL_BIND& bind)
{
	switch(_data_type) {
	  case MYSQL_TYPE_VAR_STRING:
		stmt.bind_result_column(*_string, bind);
		break;

	  case MYSQL_TYPE_LONG:
		stmt.bind_result_column(*_int, bind);
		break;

	  case MYSQL_TYPE_LONGLONG:
		stmt.bind_result_column(*_integer, bind);
		break;

	  case MYSQL_TYPE_DOUBLE:
		stmt.bind_result_column(*_number, bind);
		break;

	  case MYSQL_TYPE_DATE:
#if MYSQL_VERSION_ID>=50000
	  case MYSQL_TYPE_NEWDATE:
#endif
	  case MYSQL_TYPE_DATETIME:
	  case MYSQL_TYPE_TIME:
	  case MYSQL_TYPE_TIMESTAMP:
		stmt.bind_result_column(*_date, bind);
		break;

	  case MYSQL_TYPE_BLOB:
		stmt.bind_result_column(*_blob, bind);
		break;

	  default:
		assert(0);
	}
}

#endif // MYSQLPL_PREP_STMT


} // namespace MySqlPL
