
 //
 // mysqlpl.h
 //
 // 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 _MYSQLPL_H


#ifdef _MSC_VER
#pragma warning(disable: 4786)
#endif

#if defined(_WIN32) && !defined(_WINDOWS_)
#include <windows.h>	// for LPCTSTR
#else
#include <stdlib.h>
#include <string.h>
#endif

#include <map>
#include <string>
#include <vector>
#include <ostream>
#include <sstream>
#include <exception>


 // tested with MySQL version 4.0.24 and above (needs MySQL 4.1 to use native prepared statements)
#include <mysql.h>

#ifdef _MSC_VER
#ifndef	_NO_COMMENT
#pragma comment(lib, "libmySQL")

#if defined(_DEBUG) && defined(_DLL)	// DEBUG version only supported with MSVCRTD
#pragma comment(lib, "mysqlpld")
#else
#ifdef _DLL
#pragma comment(lib, "mysqlpl")
#elif defined(_MT)
#pragma comment(lib, "mysqlplt")
#else
#pragma comment(lib, "mysqlpll")
#endif
#endif

#endif // _NO_COMMENT

#define LONGLONG __int64

#else  // _MSC_VER

#define LONGLONG long long

#endif // _MSC_VER


namespace MySqlPL {


#ifndef MYSQLPL_PREP_STMT
#if MYSQL_VERSION_ID>=41000
#define MYSQLPL_PREP_STMT	1
#else
#define MYSQLPL_PREP_STMT	0
#endif
#endif


 /// MySQL Errorhandling

struct SqlStatement;

struct MSqlException : public std::exception
{
	typedef std::exception super;

	MSqlException(MYSQL* db, const char* file, int line);
#if MYSQLPL_PREP_STMT
	MSqlException(MYSQL_STMT* s, const char* file, int line);
	MSqlException(MYSQL_STMT* s, SqlStatement& stmt, const char* file, int line);
#endif
	MSqlException(const char* msg, const char* file, int line);

	~MSqlException() throw () {}

	virtual const char* what() const throw () {return _msg.c_str();}

	unsigned	_sql_error_code;
	std::string	_msg;
#if MYSQLPL_PREP_STMT
	std::string	_sqlstate;
#endif
	std::string	_last_sql;
	const char*	_file;
	int			_line;
};

#ifndef MYSQLPL_EXCEPTION
#define	MYSQLPL_EXCEPTION MySqlPL::MSqlException
#endif

#define	THROW_MYSQLPL_EXCEPTION(par) throw MYSQLPL_EXCEPTION(par, __FILE__, __LINE__)


 /// error handling functions

inline void mysqlpl_check_error(MYSQL* db)
{
	unsigned int error = mysql_errno(db);

	if (error != 0)
		THROW_MYSQLPL_EXCEPTION(db);
}

inline void mysqlpl_check_error(MYSQL* db, int res)
{
	if (res) {
		unsigned int error = mysql_errno(db);

		if (error != 0)
			THROW_MYSQLPL_EXCEPTION(db);
	}
}

inline void mysqlpl_check_error(MYSQL* db, my_bool b)
{
	if (b) {
		unsigned int error = mysql_errno(db);

		if (error != 0)
			THROW_MYSQLPL_EXCEPTION(db);
	}
}

#if MYSQLPL_PREP_STMT
inline void mysqlpl_check_error(MYSQL_STMT* s)
{
	unsigned int error = mysql_stmt_errno(s);

	if (error != 0)
		THROW_MYSQLPL_EXCEPTION(s);
}

inline void mysqlpl_check_error(MYSQL_STMT* s, int res)
{
	if (res) {
		unsigned int error = mysql_stmt_errno(s);

		if (error != 0)
			THROW_MYSQLPL_EXCEPTION(s);
	}
}

inline void mysqlpl_check_error(MYSQL_STMT* s, my_bool b)
{
	if (b) {
		unsigned int error = mysql_stmt_errno(s);

		if (error != 0)
			THROW_MYSQLPL_EXCEPTION(s);
	}
}
#endif


struct MSqlEnv
{
	MSqlEnv(MYSQL* mysql=NULL)
	{
		_mysql = mysql_init(NULL);
	}

	~MSqlEnv()
	{
		mysql_close(_mysql);
	}

	operator MYSQL*() const {return _mysql;}

protected:
	MYSQL*	_mysql;
};


 /// simple MySQL login to database

struct MSqlConnection
{
	MSqlConnection(MSqlEnv& env)
	 :	_env(env),
		_db(NULL),
		_connected(false)
	{
	}

	MSqlConnection(MSqlEnv& env, const char* username, const char* password, const char* db_name,
					const char* host="localhost", unsigned port=3306, unsigned long clientflag=0)
	 :	_env(env),
		_db(NULL),
		_connected(false)
	{
		connect(username, password, db_name, host, port, clientflag);
	}

	void connect(const char* username, const char* password, const char* db_name,
					const char* host="localhost", unsigned port=3306, unsigned long clientflag=0);

	~MSqlConnection()
	{
		/*see MSqlEnv::~MSqlEnv()
		if (_connected)
			mysql_close(_mysql);
		*/
	}


#if MYSQLPL_PREP_STMT
	 // get warnings
	unsigned get_warnings_count() const;

	std::string get_info() const;

	void print_warnings(std::ostream& out)
	{
		while(get_warnings_count())
			out << get_info() << std::endl;
	}
#else
	const char* get_info() const;

	void print_warnings(std::ostream& out)
	{
		const char* info = get_info();

		if (info)
			out << info << std::endl;
	}
#endif

	 // get last generated insert ID
	my_ulonglong get_insert_id() const
	{
		return mysql_insert_id(_db);
	}


	void commit()
	{
#if MYSQLPL_PREP_STMT
		my_bool res = mysql_commit(_db);
#else
		my_bool res = mysql_query(_db, "commit");
#endif
		mysqlpl_check_error(_env, res);
	}

	void rollback()
	{
#if MYSQLPL_PREP_STMT	//@@
		my_bool res = mysql_rollback(_db);
#else
		my_bool res = mysql_query(_db, "rollback");
#endif
		mysqlpl_check_error(_env, res);
	}

	operator MYSQL*() const {return _db;}

protected:
	MSqlEnv& _env;
	MYSQL*	_db;

	bool	_connected;

private:
	 // disallow copy constructor usage
	MSqlConnection(const MSqlConnection&);
};


 /// column description

struct ColumnType
{
	std::string		_table_name;
	std::string		_name;
	enum_field_types _data_type;
	int				_width;
	int				_decimals;
	int				_flags;

	ColumnType()
	{
		_data_type = (enum_field_types)-1;
		_width = 0;
		_decimals = 0;
		_flags = 0;
	}

	std::string get_type_str(bool show_null=false) const;

	void init(const MYSQL_FIELD*);
};


 /// indicator variable

struct SqlInd
{
	SqlInd(my_bool is_null=1)
	 : _is_null(is_null)
	{
	}

	operator my_bool() const {return _is_null;}
	operator my_bool*() {return &_is_null;}

	bool is_null() const {return _is_null!=0;}
	bool is_not_null() const {return _is_null==0;}

	void clear() {_is_null = 1;}
	void set() {_is_null = 0;}

protected:
	my_bool	_is_null;
};


struct SqlValue
{
	SqlValue() {}
	SqlValue(const SqlValue& other) : _ind(other._ind) {}

	bool is_null() const {return _ind.is_null();}
	bool is_not_null() const {return _ind.is_not_null();}

	const SqlInd& ind() const {return _ind;}
	SqlInd& ind() {return _ind;}

protected:
	SqlInd	_ind;
};


#if MYSQLPL_PREP_STMT

 /// int with indicator variable

struct SqlInt : public SqlValue
{
	SqlInt()	// NULL constructor
	{
	}

	SqlInt(int value)
	 :	_value(value)
	{
		_ind.set();
	}

	SqlInt(const SqlInt& other)
	 :	SqlValue(other),
		_value(other._value)
	{
	}

	enum {INT_NULL = -1};

	operator int() const {return _ind.is_null()? INT_NULL: _value;}
	int& get_ref() {return _value;}

	std::string str() const;

protected:
	int		_value;
};


 /// 64 bit integer with indicator variable

struct SqlInteger : public SqlValue
{
	SqlInteger()	// NULL constructor
	{
	}

	SqlInteger(int value)
	 :	_value(value)
	{
		_ind.set();
	}

	SqlInteger(const SqlInteger& other)
	 :	SqlValue(other),
		_value(other._value)
	{
	}

	enum {INT_NULL = -1};

	operator LONGLONG() const {return _ind.is_null()? INT_NULL: _value;}
	LONGLONG& get_ref() {return _value;}

	std::string str() const;

protected:
	LONGLONG _value;
};


struct SqlString : public SqlValue
{
	SqlString(int blen=2000)
	 :	_blen(blen),
		_str((char*)malloc((blen+1)*sizeof(char)))
	{
		*_str = '\0';
	}

	SqlString(const char* s, int blen=2000)
	 :	_blen(blen),
		_str((char*)malloc((blen+1)*sizeof(char)))
	{
		operator=(s);
	}

	SqlString(const SqlString& other)
	 :	SqlValue(other),
		_blen(other._blen),
		_str((char*)malloc((other._blen+1)*sizeof(char)))
	{
		strcpyn(_str, other._str, _blen+1);
	}

	~SqlString()
	{
		free(_str);
	}

	int len() const {return is_null()? 0: strlen(_str);}
	int	blen() const {return _blen;}
	int	alen() const {return _blen + 1;}

	void resize(int l) {_blen = l; _str = (char*)realloc(_str, (l+1)*sizeof(char));}
	void clear() {*_str = '\0'; _ind.clear();}

	char* str() {return _str;}	// Note: _ind is not checked by this function.
	char* str(int l) {resize(l); return _str;}	// Note: _ind is not changed by this function.

	const char* c_str() const {return is_null()? "": _str;}
	operator const char*() const {return is_null()? "": _str;}

	SqlString& operator=(const char* s)
	{
		if (s) {
			strcpy(_str, s);
			_ind.set();
		} else {
			*_str = '\0';
			_ind.clear();
		}

		return *this;
	}

	SqlString& operator=(const std::string& s)
	{
		strcpyn(_str, c_str(), blen()+1);
		_ind.set();

		return *this;
	}

	friend std::ostream& operator<<(std::ostream& os, const SqlString& str) {if (str.is_not_null()) os << str._str; return os;}

	static inline char* strcpyn(char* dest, const char*source, size_t count)
	{
		char* d = dest;

		for(const char* s=source; count&&(*d++=*s++); )
			count--;

		return dest;
	}

protected:
	short	_blen;
	char*	_str;
};


 /// wrapper for MYSQL_TIME

struct SqlDate : public MYSQL_TIME, public SqlValue
{
	typedef MYSQL_TIME super;

	SqlDate(enum_mysql_timestamp_type type=MYSQL_TIMESTAMP_DATETIME) {super::time_type = type;}
	SqlDate(const char* str, enum_mysql_timestamp_type type=MYSQL_TIMESTAMP_DATETIME);
	SqlDate(const SqlDate& other, enum_mysql_timestamp_type type);

	enum_field_types get_data_type() const
	{
		switch(super::time_type) {
		  case MYSQL_TIMESTAMP_NONE:		return MYSQL_TYPE_NULL;
		  case MYSQL_TIMESTAMP_DATE:		return MYSQL_TYPE_DATE;
		  case MYSQL_TIMESTAMP_TIME:		return MYSQL_TYPE_TIME;
		  case MYSQL_TIMESTAMP_DATETIME:	return MYSQL_TYPE_DATETIME;
		  default:							return (enum_field_types)-1;
		}
	}

	std::string str() const;
};


struct SqlNumber : public SqlValue
{
	SqlNumber() {}
	SqlNumber(int i) {_value = i; _ind.set();}
	SqlNumber(LONGLONG l) {_value = (double)l; _ind.set();}
	SqlNumber(double d) {_value = d; _ind.set();}
	SqlNumber(const char* s);

	void* get_ref() {return &_value;}

	std::string str() const;

	operator double*() {return &_value;}
	operator const double*() const {return &_value;}

	int get_int() const
	{
		return _ind.is_not_null()? (int)(_value+.5): 0;
	}

#ifdef _MSC_VER
	__int64 get_int64() const
	{
		return _ind.is_not_null()? (__int64)(_value+.5): 0;
	}
#endif

	double get_double() const
	{
		return _ind.is_not_null()? _value: 0.;
	}

protected:
	double	_value;
};


struct MSqlBlob : public SqlValue
{
	MSqlBlob(int len=0x10000)
	 :	_len(len),
		_ptr(NULL),
		_own(true)
	{
	}

	MSqlBlob(void* ptr, int len)
	 :	_len(len),
		_ptr(ptr),
		_own(false)
	{
	}

	MSqlBlob(const MSqlBlob& other)
	 :	SqlValue(other),
		_len(other._len),
		_own(true)
	{
		if (other._ptr)
			memcpy(_ptr=malloc(other._len), other._ptr, other._len);
		else
			_ptr = NULL;
	}

	~MSqlBlob()
	{
		if (_own)
			free(_ptr);
	}

	void* get_ref()
	{
		if (_own && !_ptr)	// delayed buffer allocation
			_ptr = malloc(_len);

		return _ptr;
	}

	int get_len() const {return _len;}
	unsigned long& get_len_ref() {return _len;}

	std::string str() const;

protected:
	unsigned long _len;
	void*	_ptr;
	bool	_own;
};

#endif


#if MYSQL_VERSION_ID<41000	//@@
#define MYSQL_TYPE_DECIMAL		FIELD_TYPE_DECIMAL
#define MYSQL_TYPE_TINY			FIELD_TYPE_TINY
#define MYSQL_TYPE_SHORT		FIELD_TYPE_SHORT
#define MYSQL_TYPE_LONG			FIELD_TYPE_LONG
#define MYSQL_TYPE_FLOAT		FIELD_TYPE_FLOAT
#define MYSQL_TYPE_DOUBLE		FIELD_TYPE_DOUBLE
#define MYSQL_TYPE_NULL			FIELD_TYPE_NULL
#define MYSQL_TYPE_TIMESTAMP	FIELD_TYPE_TIMESTAMP
#define MYSQL_TYPE_LONGLONG		FIELD_TYPE_LONGLONG
#define MYSQL_TYPE_INT24		FIELD_TYPE_INT24
#define MYSQL_TYPE_DATE			FIELD_TYPE_DATE
#define MYSQL_TYPE_TIME			FIELD_TYPE_TIME
#define MYSQL_TYPE_DATETIME		FIELD_TYPE_DATETIME
#define MYSQL_TYPE_YEAR			FIELD_TYPE_YEAR
#define MYSQL_TYPE_ENUM			FIELD_TYPE_ENUM
#define MYSQL_TYPE_SET			FIELD_TYPE_SET
#define MYSQL_TYPE_TINY_BLOB	FIELD_TYPE_TINY_BLOB
#define MYSQL_TYPE_MEDIUM_BLOB	FIELD_TYPE_MEDIUM_BLOB
#define MYSQL_TYPE_LONG_BLOB	FIELD_TYPE_LONG_BLOB
#define MYSQL_TYPE_BLOB			FIELD_TYPE_BLOB
#define MYSQL_TYPE_VAR_STRING	FIELD_TYPE_VAR_STRING
#define MYSQL_TYPE_STRING		FIELD_TYPE_STRING
#define MYSQL_TYPE_GEOMETRY		FIELD_TYPE_GEOMETRY
#define MYSQL_TYPE_BIT			FIELD_TYPE_BIT
/* only since MySQL 5.0
#define MYSQL_TYPE_NEWDECIMAL	FIELD_TYPE_NEWDECIMAL
#define MYSQL_TYPE_NEWDATE		FIELD_TYPE_NEWDATE
*/
#endif

struct BindPar
{
	void*		_ptr;
	int			_len;
	enum_field_types _type;
	my_bool*	_pind;
	unsigned long*_plen;

	BindPar()
	{
		_ptr	= NULL;
		_len	= 0;
		_type	= (enum_field_types)-1;
		_pind	= NULL;
		_plen	= NULL;
	}

	BindPar(const char* buffer, int len=-1, enum_field_types type=MYSQL_TYPE_VAR_STRING, my_bool* pind=NULL)
	{
		_ptr	= (void*) buffer;
		if (len == -1)	// not applicable for OUT variables
			_len = strlen(buffer);
		else
			_len = len;
		_type	= type;
		_pind	= pind;
		_plen	= NULL;
	}

	BindPar(const std::string& str)
	{
		_ptr	= (void*) str.c_str();
		_len	= str.length();
		_type	= MYSQL_TYPE_VAR_STRING;
		_pind	= NULL;
		_plen	= NULL;
	}

#ifdef ISSD_EXP
	BindPar(const String& str)
	{
		_ptr	= (void*) str.c_str();
		_len	= str.len();
		_type	= MYSQL_TYPE_VAR_STRING;
		_pind	= NULL;
		_plen	= NULL;
	}
#endif

	BindPar(const int& var, my_bool* pind=NULL)
	{
		_ptr	= (void*)&var;
		_len	= sizeof(var);
		_type	= MYSQL_TYPE_LONG;
		_pind	= pind;
		_plen	= NULL;
	}

	BindPar(const double& var, my_bool* pind=NULL)
	{
		_ptr	= (void*)&var;
		_len	= sizeof(var);
		_type	= MYSQL_TYPE_DOUBLE;
		_pind	= pind;
		_plen	= NULL;
	}

#if MYSQLPL_PREP_STMT
	BindPar(SqlInt& var)
	{
		int& ref = var.get_ref();

		_ptr	= &ref;
		_len	= sizeof(ref);
		_type	= MYSQL_TYPE_LONG;
		_pind	= var.ind();
		_plen	= NULL;
	}

	BindPar(SqlInteger& var)
	{
		LONGLONG& ref = var.get_ref();

		_ptr	= &ref;
		_len	= sizeof(ref);
		_type	= MYSQL_TYPE_LONGLONG;
		_pind	= var.ind();
		_plen	= NULL;
	}

	BindPar(const SqlString& str)
	{
		_ptr	= (void*) str.c_str();
		_len	= str.len();
		_type	= MYSQL_TYPE_VAR_STRING;
		_pind	= NULL;
		_plen	= NULL;
	}

	BindPar(SqlString& str)
	{
		_ptr	= (void*) str.str();
		_len	= str.alen();
		_type	= MYSQL_TYPE_VAR_STRING;
		_pind	= str.ind();
		_plen	= NULL;
	}

	BindPar(SqlDate& date)
	{
		_ptr	= &date;
		_len	= sizeof(MYSQL_TIME);
		_type	= date.get_data_type();
		_pind	= date.ind();
		_plen	= NULL;
	}

	BindPar(SqlNumber& num)
	{
		_ptr	= num.get_ref();
		_len	= sizeof(double);
		_type	= MYSQL_TYPE_DOUBLE;
		_pind	= num.ind();
		_plen	= NULL;
	}

	BindPar(MSqlBlob& blob)
	{
		_ptr	= blob.get_ref();
		_len	= blob.get_len();
		_type	= MYSQL_TYPE_BLOB;
		_pind	= blob.ind();
		_plen	= &blob.get_len_ref();
	}
#endif
};


#if MYSQLPL_PREP_STMT

 /// factory for call parameters for MySQL define functions

struct BindResultPar
{
	void*		_ptr;
	int			_len;
	enum_field_types _type;
	my_bool*	_pind;
	unsigned long*_plen;

	BindResultPar(SqlInt& var)
	{
		int& ref = var.get_ref();

		_ptr	= &ref;
		_len	= sizeof(ref);
		_type	= MYSQL_TYPE_LONG;
		_pind	= var.ind();
		_plen	= NULL;
	}

	BindResultPar(SqlInteger& var)
	{
		LONGLONG& ref = var.get_ref();

		_ptr	= &ref;
		_len	= sizeof(ref);
		_type	= MYSQL_TYPE_LONGLONG;
		_pind	= var.ind();
		_plen	= NULL;
	}

	BindResultPar(int& var, my_bool* pind=NULL)
	{
		_ptr	= &var;
		_len	= sizeof(var);
		_type	= MYSQL_TYPE_LONG;
		_pind	= pind;
		_plen	= NULL;
	}

	BindResultPar(const char* buffer, int len, enum_field_types type=MYSQL_TYPE_VAR_STRING, my_bool* pind=NULL)
	{
		_ptr	= (void*) buffer;
		_len	= len;
		_type	= type;
		_pind	= pind;
		_plen	= NULL;
	}

	BindResultPar(SqlString& str)
	{
		_ptr	= str.str();
		_len	= str.alen();
		_type	= MYSQL_TYPE_VAR_STRING;
		_pind	= str.ind();
		_plen	= NULL;
	}

	BindResultPar(SqlNumber& num)
	{
		_ptr	= num.get_ref();
		_len	= sizeof(double);
		_type	= MYSQL_TYPE_DOUBLE;
		_pind	= num.ind();
		_plen	= NULL;
	}

#if MYSQLPL_PREP_STMT
	BindResultPar(SqlDate& date)
	{
		_ptr	= &date;
		_len	= sizeof(MYSQL_TIME);
		_type	= date.get_data_type();
		_pind	= date.ind();
		_plen	= NULL;
	}
#endif

	BindResultPar(MSqlBlob& blob)
	{
		_ptr	= blob.get_ref();
		_len	= blob.get_len();
		_type	= MYSQL_TYPE_BLOB;
		_pind	= blob.ind();
		_plen	= &blob.get_len_ref();
	}
};


struct SqlVariant
{
	SqlVariant()
	 :	_data_type((enum_field_types)-1)
	{
	}

	SqlVariant(enum_field_types data_type);

	SqlVariant(const SqlVariant& other);

	~SqlVariant() {clear();}

	SqlVariant(const SqlString& x)
	{
		_string = new SqlString(x);
		_data_type = MYSQL_TYPE_VAR_STRING;
	}

	SqlVariant(const char* x)
	{
		_string = new SqlString(x);
		_data_type = MYSQL_TYPE_VAR_STRING;
	}

	SqlVariant(const SqlInt& x)
	{
		_int = new SqlInt(x);
		_data_type = MYSQL_TYPE_LONG;
	}

	SqlVariant(const SqlInteger& x)
	{
		_integer = new SqlInteger(x);
		_data_type = MYSQL_TYPE_LONGLONG;
	}

	SqlVariant(const SqlNumber& x)
	{
		_number = new SqlNumber(x);
		_data_type = MYSQL_TYPE_DOUBLE;
	}

#if MYSQLPL_PREP_STMT
	SqlVariant(const SqlDate& x)
	{
		_date = new SqlDate(x);
		_data_type = x.get_data_type();
	}
#endif

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

	int get_data_type() const
		{return _data_type;}

	SqlVariant& operator=(const SqlVariant& other) {assign(other); return *this;}

	std::string	str() const;

	SqlNumber number() const;
	int get_int() const;
#ifdef _MSC_VER
	__int64 get_int64() const;
#endif
	double get_double() const;

#if MYSQLPL_PREP_STMT
	SqlDate date(enum_mysql_timestamp_type type=MYSQL_TIMESTAMP_DATE) const;
	SqlDate datetime() const {return date(MYSQL_TIMESTAMP_DATETIME);}
	SqlDate time() const {return date(MYSQL_TIMESTAMP_TIME);}
#endif

	bool is_null() const;
	bool is_not_null() const {return !is_null();}

#if MYSQLPL_PREP_STMT
	void bind_result_column(SqlStatement& stmt, MYSQL_BIND& bind);
#endif

protected:
	union {
		SqlString*	_string;
		SqlInt*		_int;
		SqlInteger*	_integer;
		SqlNumber*	_number;
#if MYSQLPL_PREP_STMT
		SqlDate*	_date;
#endif
		MSqlBlob*	_blob;
	};

	enum_field_types _data_type;
};

#endif


struct ColumnData {
#if MYSQLPL_PREP_STMT
	SqlVariant	_column;
#endif
	ColumnType	_type;
};

typedef std::vector<ColumnData> ColumnVector;


struct BindVector : public std::vector<BindPar>
{
	void	bind(int idx, const BindPar& par);
};


#if MYSQLPL_PREP_STMT
typedef std::vector<MYSQL_BIND> MSqlBindVector;

void mysql_bind_param(const BindPar& par, MYSQL_BIND& bind);
#endif


 /// a record to hold return columns for a given statement

struct SqlRecord
{
	SqlRecord(SqlStatement& stmt);

	int get_column_count() const
		{return _columns.size();}

#if MYSQLPL_PREP_STMT
	const SqlVariant& get_column(int idx) const
		{return _columns[idx]._column;}

	const SqlVariant& get_column(const std::string& col_name) const;
#endif

	const ColumnType& get_column_type(int idx) const
		{return _columns[idx]._type;}

	const ColumnType& get_column_type(const std::string& col_name) const;

	void print_columns(std::ostream& out);
	void print_values(std::ostream& out);

#ifdef _XMLSTORAGE_H
	void dump_values(XMLStorage::XMLPos& out);
#endif

protected:
	SqlStatement&	_stmt;
	ColumnVector	_columns;

	typedef std::map<std::string, int> NameMap;
	mutable NameMap	_name_map;	// column index cache

	NameMap::const_iterator find_column(const std::string& col_name) const;
	int get_column_idx(const std::string& col_name) const;
};


#if !MYSQLPL_PREP_STMT

struct SqlResult : public SqlRecord
{
	MYSQL_ROW	_row;

	SqlResult(SqlStatement& stmt)
	 :	SqlRecord(stmt),
		_row(NULL)
	{
	}

	bool is_null(int idx) const
	{
		return _row[idx] == NULL;
	}
	int is_null(const std::string& col_name) const
	{
		return is_null(get_column_idx(col_name));
	}

	bool is_not_null(int idx) const
	{
		return _row[idx] != NULL;
	}
	int is_not_null(const std::string& col_name) const
	{
		return is_not_null(get_column_idx(col_name));
	}

	int get_int(int idx) const
	{
		return atoi(_row[idx]);
	}
	int get_int(const std::string& col_name) const
	{
		return get_int(get_column_idx(col_name));
	}

#ifdef _MSC_VER
	__int64 get_int64(int idx) const
	{
		return _atoi64(_row[idx]);
	}
	__int64 get_int64(const std::string& col_name) const
	{
		return get_int64(get_column_idx(col_name));
	}
#endif

	double get_double(int idx) const
	{
		return atof(_row[idx]);
	}
	double get_double(const std::string& col_name) const
	{
		return get_double(get_column_idx(col_name));
	}

	std::string get_string(int idx) const
	{
		const char* str = _row[idx];
		return str? str: std::string();
	}
	std::string get_string(const std::string& col_name) const
	{
		return get_string(get_column_idx(col_name));
	}

#if MYSQLPL_PREP_STMT // never used
	SqlDate get_date(int idx, enum_mysql_timestamp_type type=MYSQL_TIMESTAMP_DATE) const
	{
		return get_column(idx).date(type);
	}
	SqlDate get_date(const std::string& col_name, enum_mysql_timestamp_type type=MYSQL_TIMESTAMP_DATE) const
	{
		return get_date(get_column_idx(col_name));
	}

	SqlDate get_datetime(int idx) const
	{
		return get_column(idx).date(MYSQL_TIMESTAMP_DATETIME);
	}
	SqlDate get_datetime(const std::string& col_name) const
	{
		return get_column(col_name).date(MYSQL_TIMESTAMP_DATETIME);
	}
#endif
};

#endif


struct SqlQuery
{
	explicit SqlQuery(const char* sql)
	 :	_sql(sql)
	{
	}

	void where(const char* cond)
	{
		if (_cond.empty())
			_cond += "\nwhere	";
		else
			_cond += "\nand	";

		_cond += cond;
	}

	std::string get_sql() const
	{
		return _sql + _cond;
	}

	void bind_param(BindPar par);

	const BindVector& get_params() const
	{
		return _param_bind_vector;
	}

protected:
	std::string	_sql;
	std::string	_cond;

	BindVector	_param_bind_vector;
};


 /// a MySQL SQL statement

struct SqlStatement
{
	SqlStatement(MSqlConnection& conn);
	SqlStatement(MSqlConnection& conn, const std::string& sql);
	SqlStatement(MSqlConnection& conn, const SqlQuery& query);

	~SqlStatement();


protected:

	MYSQL*	_db;

#if MYSQLPL_PREP_STMT
	MYSQL_STMT*	_stmt;

	MSqlBindVector	_result_bind_vector;
#else
	std::string	_sql;

	std::string	create_bound_sql() const;
#endif

	BindVector	_param_bind_vector;

	mutable MYSQL_RES* _result;

	enum STATE {
		UNINITIALIZED=0,
#if MYSQLPL_PREP_STMT
		PREPARED=1, RESULT_BOUND=2,
#endif
		EXECUTED=4, FETCHED=8,
		//EOF_DATA=16, STMT_ERROR=32
	};
	int	_state;

	std::string	_last_sql;

	struct SqlResult* _res;

	int _stmt_type;


public:

	//bool eof() const {return _state < EOF_DATA;}


	 // Preparation of a SQL statements

	void prepare(const std::string& sql);


	const std::string& get_last_sql() const
	{
		return _last_sql;
	}


	 // get number of processed rows

	my_ulonglong get_row_count() const;


	 // Binding of input and output variables

	void bind_param(int idx, BindPar par);

//TODO implement named parameter binding

	void bind_params(const BindVector& params)
	{
		_param_bind_vector = params;
	}

	 // Definition of variables to receive query results

#if MYSQLPL_PREP_STMT
	void bind_result_column(BindResultPar par, MYSQL_BIND& bind);
	void bind_result(MYSQL_BIND* bind_array);
#endif


	 // define all columns of a complete recordset

#if MYSQLPL_PREP_STMT
	void bind_result(ColumnVector& columns);
#endif


	void execute();

	void execute(const std::string& sql)
	{
		prepare(sql);
		execute();
	}

	void execute(const SqlQuery& query)
	{
		prepare(query.get_sql());
		bind_params(query.get_params());
		execute();
	}

	bool execute_fetch()
	{
		execute();

		return fetch();
	}

	bool execute_fetch(const std::string& sql)
	{
		prepare(sql);

		return execute_fetch();
	}

	bool fetch()
	{
		if (!(_state & EXECUTED))
			execute();

#if MYSQLPL_PREP_STMT
		int res = mysql_stmt_fetch(_stmt);

		if (res == MYSQL_NO_DATA)
			return false;

		mysqlpl_check_error(_stmt, res);
#else
		_res->_row = mysql_fetch_row(_result);

		if (!_res->_row)
			return false;
#endif

		_state |= FETCHED;

		return true;
	}

	MYSQL_RES* get_meta_res() const;

	bool is_select() const;

	int get_column_count() const;

	void get_column_type(int idx, ColumnType& info) const;

	void print_results(std::ostream& out, bool header=true);

	void print_results(const std::string& sql, std::ostream& out, bool header=true)
	{
		execute(sql);

		print_results(out, header);
	}


#ifdef _XMLSTORAGE_H
	void dump_results(XMLStorage::XMLPos& idx, const XMLStorage::XS_String& element_name);

	void dump_results(const std::string& sql, XMLStorage::XMLPos& idx, const XMLStorage::XS_String& element_name)
	{
		execute(sql);

		dump_results(idx, element_name);
	}
#endif


	SqlResult* operator->()
	{
		if (!_res)
			fetch();

		return _res;
	}
};


#if MYSQLPL_PREP_STMT

 /// class to retrieve query result values

struct SqlResult : public SqlRecord
{
	SqlResult(SqlStatement& stmt)
	 :	SqlRecord(stmt)
	{
#if MYSQLPL_PREP_STMT
		stmt.bind_result(_columns);
#endif
	}

	bool is_null(int idx) const
	{
		return get_column(idx).is_null();
	}
	int is_null(const std::string& col_name) const
	{
		return get_column(col_name).is_null();
	}

	bool is_not_null(int idx) const
	{
		return get_column(idx).is_not_null();
	}
	int is_not_null(const std::string& col_name) const
	{
		return get_column(col_name).is_not_null();
	}

	int get_int(int idx) const
	{
		return get_column(idx).get_int();
	}
	int get_int(const std::string& col_name) const
	{
		return get_column(col_name).get_int();
	}

#ifdef _MSC_VER
	__int64 get_int64(int idx) const
	{
		return get_column(idx).get_int64();
	}
	__int64 get_int64(const std::string& col_name) const
	{
		return get_column(col_name).get_int64();
	}
#endif

	double get_double(int idx) const
	{
		return get_column(idx).get_double();
	}
	double get_double(const std::string& col_name) const
	{
		return get_column(col_name).get_double();
	}

	std::string get_string(int idx) const
	{
		return get_column(idx).str();
	}
	std::string get_string(const std::string& col_name) const
	{
		return get_column(col_name).str();
	}

#if MYSQLPL_PREP_STMT
	SqlDate get_date(int idx, enum_mysql_timestamp_type type=MYSQL_TIMESTAMP_DATE) const
	{
		return get_column(idx).date(type);
	}
	SqlDate get_date(const std::string& col_name, enum_mysql_timestamp_type type=MYSQL_TIMESTAMP_DATE) const
	{
		return get_column(col_name).date(type);
	}

	SqlDate get_datetime(int idx) const
	{
		return get_column(idx).date(MYSQL_TIMESTAMP_DATETIME);
	}
	SqlDate get_datetime(const std::string& col_name) const
	{
		return get_column(col_name).date(MYSQL_TIMESTAMP_DATETIME);
	}
#endif
};

#endif // MYSQLPL_PREP_STMT


} // namespace MySqlPL

#define	_MYSQLPL_H
#endif	// _MYSQLPL_H
