module mysqlpl;

 //
 // mysqlpl.d
 //
 // Copyright (c) 2007 Martin Fuchs <martin-fuchs@gmx.net>
 //

 /// \file mysqlpl.d
 /// MySQLPL/D 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.

*/

version(XMLSTORAGE) {
  import xmlstorage;
}

import std.stdio;
import std.stream;
import std.string;

import std.c.string; // strlen()
//static import std.c.stdlib; // atoll(), malloc()


   // C wrapper for MySQL API

  extern (C) {

	struct MYSQL;
	struct MYSQL_STMT;
	struct MYSQL_FIELD;
	struct MYSQL_RES;
	struct MYSQL_BIND;

	alias ulong my_ulonglong;
	alias char my_bool;
	alias char** MYSQL_ROW;
	alias long LONGLONG;


	enum MYSQL_FIELD_TYPES { // enum_field_types
		MYSQL_TYPE_DECIMAL,
		MYSQL_TYPE_TINY,
		MYSQL_TYPE_SHORT,
		MYSQL_TYPE_LONG,
		MYSQL_TYPE_FLOAT,
		MYSQL_TYPE_DOUBLE,
		MYSQL_TYPE_NULL,
		MYSQL_TYPE_TIMESTAMP,
		MYSQL_TYPE_LONGLONG,
		MYSQL_TYPE_INT24,
		MYSQL_TYPE_DATE,
		MYSQL_TYPE_TIME,
		MYSQL_TYPE_DATETIME,
		MYSQL_TYPE_YEAR,
		MYSQL_TYPE_NEWDATE,	// since MySQL 5.0
		MYSQL_TYPE_VARCHAR,	// since MySQL 5.0
		MYSQL_TYPE_BIT,
		MYSQL_TYPE_NEWDECIMAL	= 246,
		MYSQL_TYPE_ENUM			= 247,
		MYSQL_TYPE_SET			= 248,
		MYSQL_TYPE_TINY_BLOB	= 249,
		MYSQL_TYPE_MEDIUM_BLOB	= 250,
		MYSQL_TYPE_LONG_BLOB	= 251,
		MYSQL_TYPE_BLOB			= 252,
		MYSQL_TYPE_VAR_STRING	= 253,
		MYSQL_TYPE_STRING		= 254,
		MYSQL_TYPE_GEOMETRY		= 255
	};

	enum MYSQL_TIMESTAMP_TYPE {
		MYSQL_TIMESTAMP_NONE	= -2,
		MYSQL_TIMESTAMP_ERROR	= -1,
		MYSQL_TIMESTAMP_DATE	=  0,
		MYSQL_TIMESTAMP_DATETIME= 1,
		MYSQL_TIMESTAMP_TIME	= 2
	};


	const int MYSQL_NO_DATA			= 100;
	const int MYSQL_DATA_TRUNCATED	= 101;


	const int MYSQL_NOT_NULL_FLAG = 1;	// MySQL field can't be null

	bool IS_NOT_NULL(int flags)
	{
		return (flags&MYSQL_NOT_NULL_FLAG) != 0;
	}


	const int MYSQL_BINARY_FLAG = 128;	// MySQL result set field is binary


	 // interface to mysqlpl_fwd.cpp

	MYSQL* mysqlpl_init(MYSQL*);
	MYSQL_FIELD* mysqlpl_next_field(MYSQL_FIELD*);	// increment MYSQL_FIELD pointer using the correct structure size according to the mysql header files

	char* mysqlpl_field_get_table(in MYSQL_FIELD*);
	char* mysqlpl_field_get_name(in MYSQL_FIELD*);
	MYSQL_FIELD_TYPES mysqlpl_field_get_type(in MYSQL_FIELD*);
	int mysqlpl_field_get_length(in MYSQL_FIELD*);
	int mysqlpl_field_get_decimals(in MYSQL_FIELD*);
	int mysqlpl_field_get_flags(in MYSQL_FIELD*);

	void bindvector_free(MYSQL_BIND*);
	MYSQL_BIND* bindvector_resize(MYSQL_BIND*, int length);
	MYSQL_BIND* bindvector_next(MYSQL_BIND*);
	void bindvector_bind(MYSQL_BIND*, void* ptr, MYSQL_FIELD_TYPES type, int len, my_bool* pind, uint* plen);

  } // extern (C)

  extern (Windows) {
	MYSQL*		(*mysqlpl_real_connect)	(MYSQL* mysql, char* host, char* user, char* passwd,
										char* db, uint port, char* unix_socket, uint clientflag);
	void		(*mysqlpl_close)		(MYSQL*);

	char*		(*mysqlpl_info)			(MYSQL* db);
	uint		(*mysqlpl_errno)		(MYSQL* db);
	char*		(*mysqlpl_error)		(MYSQL* db);
	uint		(*mysqlpl_warning_count)(MYSQL* db);

	MYSQL_RES*	(*mysqlpl_store_result)	(MYSQL* db);
	void		(*mysqlpl_free_result)	(MYSQL_RES*);

	my_ulonglong (*mysqlpl_affected_rows)	(MYSQL* db);
	uint		(*mysqlpl_num_fields)		(MYSQL_RES* res);
	MYSQL_FIELD* (*mysqlpl_fetch_fields)	(MYSQL_RES*);
	MYSQL_FIELD* (*mysqlpl_fetch_field_direct)(MYSQL_RES*, int);

  version(MYSQLPL_PREP_STMT) {
	char*		(*mysqlpl_sqlstate)				(MYSQL* db);
	uint		(*mysqlpl_stmt_errno)			(MYSQL_STMT* s);
	char*		(*mysqlpl_stmt_error)			(MYSQL_STMT* s);
	char*		(*mysqlpl_stmt_sqlstate)		(MYSQL_STMT* s);
	my_bool		(*mysqlpl_autocommit)			(MYSQL* db, uint);
	MYSQL_STMT* (*mysqlpl_stmt_init)			(MYSQL*);
	int			(*mysqlpl_stmt_prepare)			(MYSQL_STMT*, char* sql, int length);
	void		(*mysqlpl_stmt_close)			(MYSQL_STMT*);
	int			(*mysqlpl_stmt_execute)			(MYSQL_STMT*);
	int			(*mysqlpl_stmt_fetch)			(MYSQL_STMT*);
	MYSQL_RES*	(*mysqlpl_stmt_store_result)	(MYSQL_STMT* s);
	my_bool		(*mysqlpl_stmt_bind_param)		(MYSQL_STMT*, MYSQL_BIND*);
	my_bool		(*mysqlpl_stmt_bind_result)		(MYSQL_STMT* s, MYSQL_BIND*);
	int			(*mysqlpl_stmt_affected_rows)	(MYSQL_STMT* s);
	MYSQL_RES*	(*mysqlpl_stmt_result_metadata)	(MYSQL_STMT* s);
  }

	my_bool		(*mysqlpl_commit)		(MYSQL* db);
	my_bool		(*mysqlpl_rollback)		(MYSQL* db);

	my_bool		(*mysqlpl_query)		(MYSQL* db, char* sql);
	MYSQL_ROW	(*mysqlpl_fetch_row)	(MYSQL_RES* res);
	my_ulonglong (*mysqlpl_insert_id)	(MYSQL* db);

  } // extern (Windows)


class vector(T)
{
public:
	this()
	{
	}

	this(int size)
	{
		_entries.length = size;

		for(int i=0; i<size; ++i)
			_entries[i] = new T;
	}

	void push_back(T entry)
	{
		_entries.length = _entries.length + 1;
		_entries[_entries.length-1] = entry;
	}

	T front()
	{
		assert(_entries.length>0);
		return _entries[0];
	}

	T back()
	{
		assert(_entries.length>0);
		return _entries[_entries.length-1];
	}

	bool empty()
	{
		return !_entries.length;
	}

	int length()
	{
		return _entries.length;
	}

	void resize(int l)
	{
		_entries.length = l;
	}

	void clear()
	{
		foreach(entry; _entries)
			delete entry;

		_entries = null;
	}

	T opIndex(int idx)
	{
		return _entries[idx];
	}

	void opIndexAssign(int idx, T value)
	{
		_entries[idx] = value;
	}

	int opApply(int delegate(inout T) dg)
	{
		int result = 0;

		foreach(e; _entries) {
			result = dg(e);

			if (result)
				break;
		}

		return result;
	}

protected:
	T[]	_entries;
};


 /// MySQL Errorhandling

class MSqlException : public Exception
{
	this(MYSQL* db/*, in char[] file, int line*/)
	{
		_sql_error_code = mysqlpl_errno(db);
		char* error_str = mysqlpl_error(db);

	  version(MYSQLPL_PREP_STMT) {
		char* state_str = mysqlpl_sqlstate(db);

		super(format("MySQL error %d - SQLSTATE %s: %s",
				_sql_error_code, state_str[0..strlen(state_str)], error_str[0..strlen(error_str)]));

		_sqlstate = state_str[0 .. state_str?strlen(state_str):0];
	  } else {
		super(format("MySQL error %d: %s", _sql_error_code, error_str));
	  }

	//	_file = file;
	//	_line = line;
	}

  version(MYSQLPL_PREP_STMT) {
	this(MYSQL_STMT* s/*, in char[] file, int line*/)
	{
		_sql_error_code = mysqlpl_stmt_errno(s);
		char* error_str = mysqlpl_stmt_error(s);

	  version(MYSQLPL_PREP_STMT) {
		char* state_str = mysqlpl_stmt_sqlstate(s);

		super(format("MySQL error %d - SQLSTATE %s: %s",
				_sql_error_code, state_str[0..strlen(state_str)], error_str[0..strlen(error_str)]));

		_sqlstate = state_str[0 .. state_str?strlen(state_str):0];
	  } else {
		super(format("MySQL error %d: %s", _sql_error_code, error_str));
	  }

	//	_file = file;
	//	_line = line;
	}

	this(MYSQL_STMT* s, SqlStatement stmt/*, in char[] file, int line*/)
	{
		_sql_error_code = mysqlpl_stmt_errno(s);
		char* error_str = mysqlpl_stmt_error(s);
		char* state_str = mysqlpl_stmt_sqlstate(s);

		char[] msg = format("MySQL error %d - SQLSTATE %s: %s",
						_sql_error_code, state_str[0..strlen(state_str)], error_str[0..strlen(error_str)]);

		_last_sql = stmt.get_last_sql();

		if (_last_sql.length)
			msg ~= format("\nlast SQL statement:\n%s\n", _last_sql);

		super(msg);

		_sqlstate = state_str[0 .. state_str?strlen(state_str):0];

	//	_file = file;
	//	_line = line;
	}

  } // MYSQLPL_PREP_STMT

	this(in char[] msg/*, in char* file, int line*/)
	{
		super(msg);

	//	_file = file;
	//	_line = line;
	}

	uint	_sql_error_code;

  version(MYSQLPL_PREP_STMT) {
	char[]	_sqlstate;
  }

	char[]	_last_sql;
//	char[]	_file;
//	int		_line;
};


 /// error handling functions

void mysqlpl_check_error(MYSQL* db)
{
	uint error = mysqlpl_errno(db);

	if (error != 0)
		throw new MSqlException(db);
}

void mysqlpl_check_error(MYSQL* db, int res)
{
	if (res) {
		uint error = mysqlpl_errno(db);

		if (error != 0)
			throw new MSqlException(db);
	}
}

void mysqlpl_check_error(MYSQL* db, my_bool b)
{
	if (b) {
		uint error = mysqlpl_errno(db);

		if (error != 0)
			throw new MSqlException(db);
	}
}

version(MYSQLPL_PREP_STMT) {
void mysqlpl_check_error(MYSQL_STMT* s)
{
	uint error = mysqlpl_stmt_errno(s);

	if (error != 0)
		throw new MSqlException(s);
}

void mysqlpl_check_error(MYSQL_STMT* s, int res)
{
	if (res) {
		uint error = mysqlpl_stmt_errno(s);

		if (error != 0)
			throw new MSqlException(s);
	}
}

void mysqlpl_check_error(MYSQL_STMT* s, my_bool b)
{
	if (b) {
		uint error = mysqlpl_stmt_errno(s);

		if (error != 0)
			throw new MSqlException(s);
	}
}
} // version(MYSQLPL_PREP_STMT)


class MSqlEnv
{
	this(MYSQL* mysql=null)
	{
		_mysql = mysqlpl_init(null);
	}

	~this()
	{
		mysqlpl_close(_mysql);
	}

	MYSQL* opCast() {return _mysql;}
	MYSQL* opCall() {return _mysql;}

protected:
	MYSQL*	_mysql;
};


 /// simple MySQL login to database

class MSqlConnection
{
	this(MSqlEnv env, in char[] username, in char[] password, in char[] db_name,
					in char[] host="localhost", uint port=3306, uint clientflag=0)
	{
		_env = env;
		_db = null;
		_connected = false;

		connect(username, password, db_name, host, port, clientflag);
	}

	void connect(in char[] username, in char[] password, in char[] db_name,
					in char[] host="localhost", uint port=3306, uint clientflag=0)
	{
		_connected = false;

		_db = mysqlpl_real_connect(_env(), toStringz(host), toStringz(username), toStringz(password), toStringz(db_name), port, null, clientflag);

		if (!_db)
			throw new MSqlException(_env());

		_connected = true;

	  version(MYSQLPL_PREP_STMT) {
		 // disable auto commit mode
		my_bool res = mysqlpl_autocommit(_db, 0);
		mysqlpl_check_error(_env(), res);
	  } else {
		(new SqlStatement(this)).execute("set autocommit=0");	// "set autocommit=0" is not supported before MySQL 4.1
	  }
	}

	~this()
	{
		/*see MSqlEnv::~MSqlEnv()
		if (_connected)
			mysqlpl_close(_mysql);
		*/
	}


  version(MYSQLPL_PREP_STMT) {
	 // get warnings
	uint get_warnings_count()
	{
		uint res = mysqlpl_warning_count(_db);

		if (res == -1)
			throw new MSqlException(_db);

		return res;
	}

	char[] get_info()
	{
		char* info = mysqlpl_info(_db);

		if (!info)
			throw new MSqlException(_db);

		return info[0 .. info?strlen(info):0];
	}

	void print_warnings(Stream o)
	{
		while(get_warnings_count())
			o.writeString(get_info() ~ '\n');
	}
  } else { // version(MYSQLPL_PREP_STMT)
	char[] get_info()
	{
		char* ret = mysqlpl_info(_db);

		return ret[0 .. ret?strlen(ret):0];
	}

	void print_warnings(Stream o)
	{
		char[] info = get_info();

		if (info.length)
			o.writeString(info ~ '\n');
	}
  }

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


	void commit()
	{
	  version(MYSQLPL_PREP_STMT) {
		my_bool res = mysqlpl_commit(_db);
	  } else {
		my_bool res = mysqlpl_query(_db, "commit");
	  }
		mysqlpl_check_error(_env(), res);
	}

	void rollback()
	{
	  version(MYSQLPL_PREP_STMT) {//@@
		my_bool res = mysqlpl_rollback(_db);
	  } else {
		my_bool res = mysqlpl_query(_db, "rollback");
	  }
		mysqlpl_check_error(_env(), res);
	}

	MYSQL* opCast() {return _db;}
	MYSQL* opCall() {return _db;}

protected:
	MSqlEnv _env;
	MYSQL*	_db;

	bool	_connected;
};


 /// column description

class ColumnType
{
	char[]	_table_name;
	char[]	_name;
	MYSQL_FIELD_TYPES _data_type;
	int		_width;
	int		_decimals;
	int		_flags;

	this()
	{
		_data_type = cast(MYSQL_FIELD_TYPES)-1;
		_width = 0;
		_decimals = 0;
		_flags = 0;
	}

	char[] get_type_str(bool show_null=false)
	{
        char[] str;

		switch(_data_type) {
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING:
			str = format("char(%d)", _width);
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_VAR_STRING:
			str = format("varchar(%d)", _width);
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DECIMAL:
			if (_width == 0)
				str = "decimal";
			else
				str = format("decimal(%d,%d)", _width-1, _decimals);
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_NEWDECIMAL:	// MYSQL_VERSION_ID>=50000
			if (_width == 0)
				str = "newdecimal";
			else
				str = format("newdecimal(%d,%d)", _width-1, _decimals);
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_TINY:
			str = format("tinyint(%d)", _width);
			break;
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_SHORT:
			str = format("shortint(%d)", _width);
			break;
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG:
			str = format("int(%d)", _width);
			break;
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONGLONG:
			str = format("bigint(%d)", _width);
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_ENUM:
			str = "enum";
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_FLOAT:
			if (_width == 12)
				str = "float";
			else
				str = format("float(%d,%d)", _width, _decimals);
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DOUBLE:
			if (_width == 22)
				str = "double";
			else
				str = format("double(%d,%d)", _width, _decimals);
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATE:
			str = "date";
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_NEWDATE:	// MYSQL_VERSION_ID>=50000
			str = "newdate";
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATETIME:
			str = "datetime";
			break;
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_TIME:
			str = "time";
			break;
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_YEAR:
			str = format("year(%d)", _width);
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_TINY_BLOB:
			if (_flags & MYSQL_BINARY_FLAG)
				str = "tinyblob";
			else
				str = "tinytext";
			break;
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_MEDIUM_BLOB:
			if (_flags & MYSQL_BINARY_FLAG)
				str = "mediumblob";
			else
				str = "mediumtext";
			break;
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG_BLOB:
			if (_flags & MYSQL_BINARY_FLAG)
				str = "longblob";
			else
				str = "longtext";
			break;
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_BLOB:
			if (_flags & MYSQL_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;
	}

	void init(in MYSQL_FIELD* field)
	{
		char* table = mysqlpl_field_get_table(field);
		char* name = mysqlpl_field_get_name(field);

		 // get column name
		_table_name	= table[0 .. strlen(table)];
		_name		= name[0 .. strlen(name)];

		 // get column data type
		_data_type	= mysqlpl_field_get_type(field);

		_width		= mysqlpl_field_get_length(field);
		_decimals	= mysqlpl_field_get_decimals(field);

		 // get null-ability, binary, ... flags
		_flags		= mysqlpl_field_get_flags(field);
	}
};


class ColumnData {
	this()
	{
		_type = new ColumnType;
	}

	ColumnType	_type;

  version(MYSQLPL_PREP_STMT) {
	SqlVariant	_column;
  }
};


class BindVector : public vector!(BindPar)
{
	void bind(int idx, in BindPar par)
	{
		_entries[idx] = par;
	}
};


version(MYSQLPL_PREP_STMT) {

void mysqlpl_bind_param(in BindPar par, MYSQL_BIND* bind)
{
	bindvector_bind(bind, par._ptr, par._type, par._len, par._pind, par._plen);

/* ersetzt
	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;
*/
}

}


alias vector!(ColumnData) ColumnDataVector;


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

class SqlRecord
{
	this(SqlStatement stmt)
	{
		_stmt = stmt;

		int column_cnt = stmt.get_column_count();

		_columns = new ColumnDataVector(column_cnt);

		MYSQL_FIELD* fields = mysqlpl_fetch_fields(stmt.get_meta_res());

		foreach(ColumnData col; _columns) {
			col._type.init(fields);

			fields = mysqlpl_next_field(fields);

		  version(MYSQLPL_PREP_STMT) {
			col._column = new SqlVariant(col._type._data_type);
		  }
		}

		//_name_map.clear();
	}

	int get_column_count()
	{
		return _columns.length;
	}

  version(MYSQLPL_PREP_STMT) {
	SqlVariant get_column(int idx)
	{
		return _columns[idx]._column;
	}

	SqlVariant get_column(in char[] col_name)
	{
		int* found = find_column(col_name);

		if (found)
			return _columns[*found]._column;
		else {
			throw new MSqlException("column not found");
			return null;
		}
	}
  }

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

	ColumnType get_column_type(in char[] col_name)
	{
		int* found = find_column(col_name);

		if (found)
			return _columns[*found]._type;
		else {
			throw new MSqlException("column not found");
			return null;
		}
	}

	void print_columns(Stream o)
	{
		foreach(col; _columns) {
			ColumnType type = col._type;

			if (type._table_name.length)
				o.writeString(type._table_name ~ '.');

			o.writeString(type._name ~ ": " ~ type.get_type_str() ~ '\n');
		}

		o.write('\n');
	}

	void print_values(Stream o)
	{
	  version(XMLSTORAGE) {
		foreach(col; _columns) {
			ColumnType type = col._type;

	//		if (type._table_name.length)
	//			o.writeString(type._table_name ~ ".");

			o.writeString(type._name ~ "=" ~ col._column.str() ~ '\n');
		}
	  } else {
		int i = 0;

		foreach(col; _columns) {
			ColumnType type = col._type;

	//		if (type._table_name)
	//			o.writeString(type._table_name ~ ".");

			o.writeString(type._name ~ "=" ~ _stmt().get_string(i++) ~ '\n');
		}
	  }
	}

  version(XMLSTORAGE) {
	void dump_values(XMLStorage.XMLPos o);
  }

protected:
	SqlStatement	_stmt;
	ColumnDataVector _columns;

	int[char[]]		_name_map;	// column index cache

	int* find_column(in char[] col_name)
	{
		 // initialize column name map
		if (!_name_map) {
			int idx = 0;

			foreach(col; _columns)
				_name_map[toupper(col._type._name)] = idx++;
		}

		return toupper(col_name) in _name_map;
	}

	int get_column_idx(in char[] col_name)
	{
		int* found = find_column(col_name);

		if (found)
			return *found;
		else {
			throw new MSqlException("column not found");
			//return -1;
		}
	}
};


version(MYSQLPL_PREP_STMT) {

class SqlResult : public SqlRecord
{
	this(SqlStatement stmt)
	{
		super(stmt);

	  version(MYSQLPL_PREP_STMT) {
		stmt.bind_result(_columns);
	  }
	}

	bool is_null(int idx)
	{
		return get_column(idx).is_null();
	}
	int is_null(in char[] col_name)
	{
		return get_column(col_name).is_null();
	}

	bool is_not_null(int idx)
	{
		return get_column(idx).is_not_null();
	}
	int is_not_null(in char[] col_name)
	{
		return get_column(col_name).is_not_null();
	}

	int get_int(int idx)
	{
		return get_column(idx).get_int();
	}
	int get_int(in char[] col_name)
	{
		return get_column(col_name).get_int();
	}

	long get_int64(int idx)
	{
		return get_column(idx).get_int64();
	}
	long get_int64(in char[] col_name)
	{
		return get_column(col_name).get_int64();
	}

	double get_double(int idx)
	{
		return get_column(idx).get_double();
	}
	double get_double(in char[] col_name)
	{
		return get_column(col_name).get_double();
	}

	char[] get_string(int idx)
	{
		return get_column(idx).str();
	}
	char[] get_string(in char[] col_name)
	{
		return get_column(col_name).str();
	}

  version(MYSQLPL_PREP_STMT) { // always used in this section
	SqlDate get_date(int idx, MYSQL_TIMESTAMP_TYPE type=MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_DATE)
	{
		return get_column(idx).date(type);
	}

	SqlDate get_date(in char[] col_name, MYSQL_TIMESTAMP_TYPE type=MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_DATE)
	{
		return get_column(col_name).date(type);
	}
  }
};

} else { // version(MYSQLPL_PREP_STMT)

 /// class to retrieve query result values

class SqlResult : public SqlRecord
{
	MYSQL_ROW	_row;

	this(SqlStatement stmt)
	{
		super(stmt);
		_row = null;
	}

	bool is_null(int idx)
	{
		return _row[idx] == null;
	}
	int is_null(in char[] col_name)
	{
		return is_null(get_column_idx(col_name));
	}

	bool is_not_null(int idx)
	{
		return _row[idx] != null;
	}
	int is_not_null(in char[] col_name)
	{
		return is_not_null(get_column_idx(col_name));
	}

	int get_int(int idx)
	{
		return std.c.stdlib.atoi(_row[idx]);
	}
	int get_int(in char[] col_name)
	{
		return get_int(get_column_idx(col_name));
	}

	long get_int64(int idx)
	{
		return std.c.stdlib.atoll(_row[idx]);
	}
	long get_int64(in char[] col_name)
	{
		return get_int64(get_column_idx(col_name));
	}

	double get_double(int idx)
	{
		return std.c.stdlib.atof(_row[idx]);
	}
	double get_double(in char[] col_name)
	{
		return get_double(get_column_idx(col_name));
	}

	char[] get_string(int idx)
	{
		char* str = _row[idx];
		return str? str[0..strlen(str)]: "";
	}
	char[] get_string(in char[] col_name)
	{
		return get_string(get_column_idx(col_name));
	}

  version(MYSQLPL_PREP_STMT) { // never used
	SqlDate get_date(int idx, MYSQL_TIMESTAMP_TYPE type=MYSQL_TIMESTAMP_DATE)
	{
		return get_column(idx).date(type);
	}

	SqlDate get_date(in char[] col_name, MYSQL_TIMESTAMP_TYPE type=MYSQL_TIMESTAMP_DATE)
	{
		return get_date(get_column_idx(col_name));
	}
  }
};

} // MYSQLPL_PREP_STMT


class BindPar
{
	void*		_ptr;
	int			_len;
	MYSQL_FIELD_TYPES _type;
	my_bool*	_pind;
	uint*		_plen;

	this()
	{
		_ptr	= null;
		_len	= 0;
		_type	= cast(MYSQL_FIELD_TYPES)-1;
		_pind	= null;
		_plen	= null;
	}

	this(in char[] str, my_bool* pind=null)
	{
		_ptr	= str.ptr;
		_len	= str.length;
		_type	= MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING;
		_pind	= pind;
		_plen	= null;
	}

	this(in int* var, my_bool* pind=null)
	{
		_ptr	= var;
		_len	= (*var).sizeof;
		_type	= MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG;
		_pind	= pind;
		_plen	= null;
	}

	this(in double* var, my_bool* pind=null)
	{
		_ptr	= var;
		_len	= (*var).sizeof;
		_type	= MYSQL_FIELD_TYPES.MYSQL_TYPE_DOUBLE;
		_pind	= pind;
		_plen	= null;
	}

  version(MYSQLPL_PREP_STMT) {
	this(SqlInt var)
	{
		_ptr	= var.get_ref();
		_len	= int.sizeof;
		_type	= MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG;
		_pind	= var.ind().get_ref();
		_plen	= null;
	}

	this(SqlInteger var)
	{
		_ptr	= var.get_ref();
		_len	= LONGLONG.sizeof;
		_type	= MYSQL_FIELD_TYPES.MYSQL_TYPE_LONGLONG;
		_pind	= var.ind().get_ref();
		_plen	= null;
	}

	this(in SqlString str)
	{
		_ptr	= str.c_str().ptr;
		_len	= str.len();
		_type	= MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING;
		_pind	= null;
		_plen	= null;
	}
/*@@ SqlString as output buffer
	this(SqlString str)
	{
		_ptr	= str.str().ptr;
		_len	= str.blen();//alen();
		_type	= MYSQL_FIELD_TYPES.MYSQL_TYPE_VAR_STRING;
		_pind	= str.ind().get_ref();
		_plen	= str.get_len_ref();
	}
*/
	this(SqlDate date)
	{
		_ptr	= &date._value;
		_len	= MYSQL_TIME.sizeof;
		_type	= date.get_data_type();
		_pind	= date.ind().get_ref();
		_plen	= null;
	}

	this(SqlNumber num)
	{
		_ptr	= num.get_ref();
		_len	= double.sizeof;
		_type	= MYSQL_FIELD_TYPES.MYSQL_TYPE_DOUBLE;
		_pind	= num.ind().get_ref();
		_plen	= null;
	}

	this(MSqlBlob blob)
	{
		_ptr	= blob.get_ref();
		_len	= blob.get_len();
		_type	= MYSQL_FIELD_TYPES.MYSQL_TYPE_BLOB;
		_pind	= blob.ind().get_ref();
		_plen	= cast(uint*)blob.get_len_ref();
	}
  } // version(MYSQLPL_PREP_STMT)
};


class SqlQuery
{
	this(in char[] sql)
	{
		_param_bind_vector = new BindVector;

		_sql = sql;
	}

	void where(in char[] cond)
	{
		if (!_cond.length)
			_cond ~= "\nwhere	";
		else
			_cond ~= "\nand	";

		_cond ~= cond;
	}

	char[] get_sql()
	{
		return _sql ~ _cond;
	}

	void bind_param(BindPar par)
	{
		int idx = _param_bind_vector.length();

		_param_bind_vector.resize(idx+1);

		_param_bind_vector.bind(idx, par);
	}

	BindVector get_params()
	{
		return _param_bind_vector;
	}

protected:
	char[]	_sql;
	char[]	_cond;

	BindVector	_param_bind_vector;
};


 /// a MySQL SQL statement

class SqlStatement
{
	this(MSqlConnection conn)
	{
		_db = conn();
		_state = STATE.UNINITIALIZED;
	//	_res = null;
		_stmt_type = -1;
	//	_result = null;
		_param_bind_vector = new BindVector;

	  version(MYSQLPL_PREP_STMT) {
		_stmt = mysqlpl_stmt_init(_db);
	  }
	}

	this(MSqlConnection conn, in char[] sql)
	{
		_db = conn();
		_state = STATE.UNINITIALIZED;
	//	_res = null;
		_stmt_type = -1;
	//	_result = null;
		_param_bind_vector = new BindVector;

	  version(MYSQLPL_PREP_STMT) {
		_stmt = mysqlpl_stmt_init(_db);
	  }

		prepare(sql);
	}

	this(MSqlConnection conn, in SqlQuery query)
	{
		_db = conn();
		_state = STATE.UNINITIALIZED;
	//	_res = null;
		_stmt_type = -1;
	//	_result = null;
		_param_bind_vector = new BindVector;

	  version(MYSQLPL_PREP_STMT) {
		_stmt = mysqlpl_stmt_init(_db);
	  }

		execute(query);
	}

	~this()
	{
	  version(MYSQLPL_PREP_STMT) {
		if (_stmt)
			mysqlpl_stmt_close(_stmt);

		bindvector_free(_result_bind_vector);
		_result_bind_vector = null;
	  } else {
		if (_result)
			mysqlpl_free_result(_result);
	  }
	}


protected:

	MYSQL*	_db;

  version(MYSQLPL_PREP_STMT) {
	MYSQL_STMT*	_stmt;

	MYSQL_BIND*	_result_bind_vector;
  } else {
	char[]	_sql;

	 // create sql statement from prepared statement and integrate bind variables
	char[] create_bound_sql()
	{
		char[] str = _sql;
		char[] sql;

		foreach(BindPar value; _param_bind_vector) {
			int idx = next_bind_variable(str);

			if (idx == -1)
				throw new MSqlException("too much bind values");

			sql ~= str[0 .. idx];

			switch(value._type) {
			  case MYSQL_FIELD_TYPES.MYSQL_TYPE_VAR_STRING: {
				sql ~= '\'';

				 // escape quote characters in bound string constants
				char[] p = (cast(char*)value._ptr)[0 .. value._len];

				foreach(c; p) {
					if (c == '\'')
						sql ~= "''";
					else
						sql ~= c;
				}

				sql ~= '\'';
				break;}

			  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG:
				sql ~= format("%d", *cast(int*)value._ptr);
				break;

			  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DOUBLE:
				sql ~= format("%f", *cast(double*)value._ptr);
				break;

			  default:
				throw new MSqlException("unsupported datatype");
			}

			str = str[idx+1 .. length];
		}

		if (next_bind_variable(str) != -1)
			throw new MSqlException("too less bind values");

		sql ~= str;

		return sql;
	}

	static int next_bind_variable(in char[] p)
	{
		for(int i=0; i<p.length; ++i)
			if (p[i] == '?')
				return i;
			 // look for quotes and escape characters
			else if (p[i] == '\'') {
				while(++i < p.length)
					if (p[i]=='\'' && ++i<p.length-1 && p[i]!='\'')
						break;
			}

		return -1;
	}

  }

	BindVector	_param_bind_vector;

	MYSQL_RES*	_result;

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

	char[]	_last_sql;

	SqlResult _res;

	int _stmt_type;


public:

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


	 // Preparation of a SQL statements

	void prepare(in char[] sql)
	{
		delete _res;
		_res = null;

		_last_sql = sql;

	  version(MYSQLPL_PREP_STMT) {
		int res = mysqlpl_stmt_prepare(_stmt, sql.ptr, sql.length);
		mysqlpl_check_error(_stmt, res);

		_state = STATE.PREPARED;
	  } else {
		_sql = sql;

		if (_result)
			mysqlpl_free_result(_result);
	  }

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


	char[] get_last_sql()
	{
		return _last_sql;
	}


	 // get number of processed rows

	my_ulonglong get_row_count()
	{
	  version(MYSQLPL_PREP_STMT) {
		if (is_select()) {
			if (mysqlpl_stmt_store_result(_stmt))
				throw new MSqlException(_stmt);
		}

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

		if (res == cast(my_ulonglong)-1)
			throw new MSqlException(_stmt);
	  } else {
		int res = mysqlpl_affected_rows(_db);

		if (res == -1)
			throw new MSqlException(_db);
	  }

		return res;
	}


	 // Binding of input and output variables

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

		_param_bind_vector.bind(idx, par);
	}


	void bind_param(int idx, in char[] str, my_bool* pind=null)
	{
		bind_param(idx, new BindPar(str, pind));
	}

	void bind_param(int idx, in int* var, my_bool* pind=null)
	{
		bind_param(idx, new BindPar(var, pind));
	}

	void bind_param(int idx, in double* var, my_bool* pind=null)
	{
		bind_param(idx, new BindPar(var, pind));
	}

  version(MYSQLPL_PREP_STMT) {
	void bind_param(int idx, SqlInt var)
	{
		bind_param(idx, new BindPar(var));
	}

	void bind_param(int idx, SqlInteger var)
	{
		bind_param(idx, new BindPar(var));
	}

	void bind_param(int idx, SqlString str)
	{
		bind_param(idx, new BindPar(str));
	}
/*@@ SqlString as output buffer
	void bind_param(int idx, SqlString str)
	{
		bind_param(idx, new BindPar(str));
	}
*/
	void bind_param(int idx, SqlDate date)
	{
		bind_param(idx, new BindPar(date));
	}

	void bind_param(int idx, SqlNumber num)
	{
		bind_param(idx, new BindPar(num));
	}

	void bind_param(int idx, MSqlBlob blob)
	{
		bind_param(idx, new BindPar(blob));
	}
  } // version(MYSQLPL_PREP_STMT)

	void bind_params(in BindVector params)
	{
		_param_bind_vector = params;
	}


	 // Definition of variables to receive query results

  version(MYSQLPL_PREP_STMT) {

	void bind_result_column(BindResultPar par, MYSQL_BIND* bind)
	{
		bindvector_bind(bind, par._ptr, par._type, par._len, par._pind, par._plen);

	/* ersetzt
		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 bind_result(MYSQL_BIND* bind_array)
	{
		my_bool res = mysqlpl_stmt_bind_result(_stmt, bind_array);

		mysqlpl_check_error(_stmt, res);

		if (_state & STATE.EXECUTED) {
			delete _res;
			_res = null;

			_state = (_state|STATE.RESULT_BOUND) & ~STATE.FETCHED;
		} else
			_state |= STATE.RESULT_BOUND;
	}

	 // define all columns of a complete recordset
	void bind_result(ColumnDataVector columns)
	{
		MYSQL_BIND* p = _result_bind_vector;

		foreach(col; columns) {
			col._column.bind_result_column(this, p);
			p = bindvector_next(p);
		}

		bind_result(_result_bind_vector);
	}

  }


	void execute()
	{
	  version(MYSQLPL_PREP_STMT) {
		if (!(_state & STATE.RESULT_BOUND)) {
			 // a SELECT statement without ready define variables?
			if (is_select()) {	// a statement with result set? (=> SELECT)
				_result_bind_vector = bindvector_resize(_result_bind_vector, get_column_count());

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

		MYSQL_BIND* param_mbind_vector;

		 // bind parameter values
		if (!_param_bind_vector.empty()) {
			 // Umwandlung der Bindparamater von BindPar in MYSQL_BIND-Strukturelemente
			param_mbind_vector = bindvector_resize(param_mbind_vector, _param_bind_vector.length);

			MYSQL_BIND* o = param_mbind_vector;
			foreach(i; _param_bind_vector) {
				mysqlpl_bind_param(i, o);
				o = bindvector_next(o);
			}

			my_bool res = mysqlpl_stmt_bind_param(_stmt, param_mbind_vector);

			mysqlpl_check_error(_stmt, res);
		}

		int res = mysqlpl_stmt_execute(_stmt);

		mysqlpl_check_error(_stmt, res);
	  } else {
		int res = mysqlpl_query(_db, toStringz(create_bound_sql()));

		mysqlpl_check_error(_db, res);

		if (_result)
			mysqlpl_free_result(_result);

		_result = mysqlpl_store_result(_db);

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

			_res = new SqlResult(this);
		}
	  }

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

	void execute(in char[] sql)
	{
		prepare(sql);
		execute();
	}

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

	bool execute_fetch()
	{
		execute();

		return fetch();
	}

	bool execute_fetch(in char[] sql)
	{
		prepare(sql);

		return execute_fetch();
	}

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

	  version(MYSQLPL_PREP_STMT) {
		int res = mysqlpl_stmt_fetch(_stmt);

		if (res == MYSQL_NO_DATA)
			return false;

		mysqlpl_check_error(_stmt, res);
	  } else {
		_res._row = mysqlpl_fetch_row(_result);

		if (!_res._row)
			return false;
	  }

		_state |= STATE.FETCHED;

		return true;
	}

	MYSQL_RES* get_meta_res()
	{
	  version(MYSQLPL_PREP_STMT) {
		if (!_result) {
			_result = mysqlpl_stmt_result_metadata(_stmt);

			if (!_result)
				throw new MSqlException("no result set metadata");
		}
	  }

		return _result;
	}

	bool is_select()
	{
  version(MYSQLPL_PREP_STMT) {
	if (!_result)
		_result = mysqlpl_stmt_result_metadata(_stmt);

	return _result!=null;
  } else {
	if (!(_state & STATE.EXECUTED))
		throw new MSqlException("statement not yet executed");

	return _result!=null;
  }
	}

	int get_column_count()
	{
	  version(MYSQLPL_PREP_STMT) {
		uint count = mysqlpl_num_fields(get_meta_res());

		if (count == -1u)
			throw new MSqlException(_stmt);

		return count;
	  } else {
		return mysqlpl_num_fields(_result);
	  }
	}

	void get_column_type(int idx, out ColumnType info)
	{
		MYSQL_FIELD* field = mysqlpl_fetch_field_direct(get_meta_res(), idx);

		if (!field)
	  version(MYSQLPL_PREP_STMT) {
			throw new MSqlException(_stmt);
	  } else {
			throw new MSqlException(_db);
	  }

		info.init(field);
	}

	void print_results(Stream o, bool header=true)
	{
		if (!(_state & STATE.EXECUTED))
			execute();

		if (header)
			_res.print_columns(o);

		int row_cnt = 0;

		while(fetch()) {
			++row_cnt;

			o.writeString(format("row %d:\n", row_cnt));

			_res.print_values(o);

			o.write('\n');
		}
	}

	void print_results(in char[] sql, Stream o, bool header=true)
	{
		execute(sql);

		print_results(o, header);
	}

	SqlResult opCall()
	{
		if (!_res)
			fetch();

		return _res;
	}

  version(XMLSTORAGE) {//@@
	void dump_results(XMLStorage.XMLPos idx, in char[] element_name);

	void dump_results(in char[] sql, XMLStorage.XMLPos idx, in char[] element_name)
	{
		execute(sql);

		dump_results(idx, element_name);
	}
  }

}; // SqlStatement


version(MYSQLPL_PREP_STMT) {

 /// indicator variable

class SqlInd
{
	this(my_bool is_null=1)
	{
		_is_null = is_null;
	}

	this(SqlInd other)
	{
		_is_null = other._is_null;
	}

	my_bool opCast() {return _is_null;}
	my_bool* get_ref() {return &_is_null;}

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

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

protected:
	my_bool	_is_null;
};


class SqlValue
{
	this()
	{
		_ind = new SqlInd();
	}

	this(in SqlValue other)
	{
		_ind = new SqlInd(other._ind);
	}

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

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

	SqlInd ind()
	{
		return _ind;
	}

protected:
	SqlInd	_ind;
};


 /// int with indicator variable

class SqlInt : public SqlValue
{
	this()	// null constructor
	{
	}

	this(int value)
	{
		_value = value;
		_ind.set();
	}

	this(in SqlInt other)
	{
		super(other);
		_value = other._value;
	}

	const int INT_NULL = -1;

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

	char[] str()
	{
		if (_ind.is_null())
			return "";

		return format("%d", _value);
	}

protected:
	int		_value;
};


 /// 64 bit integer with indicator variable

class SqlInteger : public SqlValue
{
	this()	// null constructor
	{
	}

	this(int value)
	{
		_value = value;

		_ind.set();
	}

	this(in SqlInteger other)
	{
		super(other);
		_value = other._value;
	}

	const int INT_NULL = -1;

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

	char[] str()
	{
		if (_ind.is_null())
			return "";

		return format("%d", _value);
	}

protected:
	LONGLONG _value;
};


class SqlString : public SqlValue
{
	this(int blen=2000)
	{
		_str.length = blen;
		_len = 0;
	}

	this(in char[] s, int blen=2000)
	{
		_str.length = blen;

		opAssign(s);

		_ind.set();
	}

	this(in SqlString other)
	{
		super(other);

		_str = other._str.dup;
		_len = other._len;
	}

	int len() {return is_null()? 0: _len;}
	int	blen() {return _str.length;}
//	int	alen() {return _str.length + 1;}

	void resize(int l) {_str.length = l;}
	void clear() {_str.length = 0; _ind.clear();}

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

	char[] c_str() {return is_null()? "": _str[0.._len];}

	SqlString opAssign(in char[] s)
	{
		if (s) {
			_str[0 .. s.length] = s[];
			_len = s.length;
			_ind.set();
		} else {
			_len = 0;
			_ind.clear();
		}

		return this;
	}

	Stream writeString(Stream os)
	{
		if (is_not_null())
			os.writeString(_str);
			return os;
	}

protected:
	char[]	_str;
	size_t	_len;
};


struct MYSQL_TIME
{
  uint	year, month, day, hour, minute, second;
  uint	second_part;
  my_bool neg;
  MYSQL_TIMESTAMP_TYPE time_type;
};


 /// wrapper for MYSQL_TIME

class SqlDate : public SqlValue
{
	this(MYSQL_TIMESTAMP_TYPE type=MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_DATETIME)
	{
		_value.time_type = type;
	}

	this(MYSQL_TIME time, MYSQL_TIMESTAMP_TYPE type=MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_DATETIME)
	{
		_value = time;
		_value.time_type = MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_DATETIME;
	}

	this(in char[] str, MYSQL_TIMESTAMP_TYPE type=MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_DATETIME)
	{
		while(str.length>0 && std.ctype.isspace(str[0]))
			str = str[1 .. length];

		char* s = toStringz(str);

		if (s && *s) {
			switch(_value.time_type) {
			  case MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_DATE:
				if (sscanf(s, "%04d-%02d-%02d",
								&_value.year, &_value.month, &_value.day) != 3)
				if (sscanf(s, "%02d.%02d.%04d",
								&_value.day, &_value.month, &_value.year) != 3)
					throw new MSqlException("date conversion error");
				break;

			  case MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_TIME:
				_value.second_part = 0;
				if (sscanf(s, "%02d:%02d:%02d.%06d",
								&_value.hour, &_value.minute, &_value.second, &_value.second_part) < 3)
					throw new MSqlException("time conversion error");
				break;

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

			_value.time_type = type;

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

	this(in SqlDate other, MYSQL_TIMESTAMP_TYPE type)
	{
		_value = other._value;

		if (other._value.time_type != type) {
			if (type==MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_NONE || type==MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_ERROR)
				_value.time_type = type;
			else {
				if (other._value.time_type == MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_DATE) {
					if (type == MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_TIME)
						throw new MSqlException("no conversion from time to date");

					_value.hour = 0;
					_value.minute = 0;
					_value.second = 0;
				} else if (other._value.time_type == MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_TIME) {
					if (type == MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_DATE)
						throw new MSqlException("no conversion from date to time");

					_value.year = 0;
					_value.month = 0;
					_value.day = 0;
				}

				_value.time_type = type;
			}
		}
	}

	MYSQL_FIELD_TYPES get_data_type()
	{
		switch(_value.time_type) {
		  case MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_DATE:
			return MYSQL_FIELD_TYPES.MYSQL_TYPE_DATE;

		  case MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_TIME:
			return MYSQL_FIELD_TYPES.MYSQL_TYPE_TIME;

		  case MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_DATETIME:
			return MYSQL_FIELD_TYPES.MYSQL_TYPE_DATETIME;

		  default:
			return cast(MYSQL_FIELD_TYPES)-1;
		}
	}

	char[] str()
	{
		if (_ind.is_not_null()) {
			switch(_value.time_type) {
			  case MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_DATE:
				return format("%04d-%02d-%02d",
								_value.year, _value.month, _value.day);
				break;

			  case MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_TIME:
				return format("%02d:%02d:%02d.%06d",
								_value.hour, _value.minute, _value.second, _value.second_part);
				break;

			  //case MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_DATETIME:
			  default:
				return format("%04d-%02d-%02d %02d:%02d:%02d.%06d",
								_value.year, _value.month, _value.day,
								_value.hour, _value.minute, _value.second, _value.second_part);
			}
		} else
			return "";
	}

	MYSQL_TIME opCast() {return _value;}

	MYSQL_TIME	_value;
};


class SqlNumber : public SqlValue
{
	this()
	{
	}

	this(int i)
	{
		_value = i;
		_ind.set();
	}

	this(LONGLONG l)
	{
		_value = l;
		_ind.set();
	}

	this(double d)
	{
		_value = d;
		_ind.set();
	}

	this(in char[] s)
	{
		if (sscanf(toStringz(s), "%lf", &_value) == 1)	// %lf -> double
			_ind.set();
	}

//@@	void* get_ref() {return &_value;}

	char[] str()
	{
		if (_ind.is_null())
			return "";

		return format("%f", _value);	// %f -> double
	}

	double opCast() {return _value;}
	double* get_ref() {return &_value;}

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

	long get_int64()
	{
		return _ind.is_not_null()? cast(long)(_value+.5): 0;
	}

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

protected:
	double	_value;
};


class MSqlBlob : public SqlValue
{
	this(int len=0x10000)
	{
		_len = len;
		_ptr = null;
		_own = true;
	}

	this(void* ptr, int len)
	{
		_len = len;
		_ptr = ptr;
		_own = false;
	}

	this(in MSqlBlob other)
	{
		super(other);

		_len = other._len;
		_own = true;

		if (other._ptr)
			memcpy(_ptr=std.c.stdlib.malloc(other._len), other._ptr, other._len);
		else
			_ptr = null;
	}

	~this()
	{
		if (_own)
			std.c.stdlib.free(_ptr);
	}

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

		return _ptr;
	}

	int		get_len() {return _len;}
	ulong*	get_len_ref() {return &_len;}

	char[]	str()
	{
		if (_ind.is_not_null()) {
			return (cast(char*)_ptr)[0 .. _len];
		} else
			return "";
	}

protected:
	ulong	_len;
	void*	_ptr;
	bool	_own;
};


 /// factory for call parameters for MySQL define functions

class BindResultPar
{
	void*		_ptr;
	int			_len;
	MYSQL_FIELD_TYPES _type;
	my_bool*	_pind;
	uint*		_plen;

	this(SqlInt var)
	{
		_ptr	= var.get_ref();
		_len	= int.sizeof;
		_type	= MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG;
		_pind	= var.ind().get_ref();
		_plen	= null;
	}

	this(SqlInteger var)
	{
		_ptr	= var.get_ref();
		_len	= LONGLONG.sizeof;
		_type	= MYSQL_FIELD_TYPES.MYSQL_TYPE_LONGLONG;
		_pind	= var.ind().get_ref();
		_plen	= null;
	}

	this(int* var, my_bool* pind=null)
	{
		_ptr	= var;
		_len	= int.sizeof;
		_type	= MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG;
		_pind	= pind;
		_plen	= null;
	}

	this(in char[] buffer, int len, MYSQL_FIELD_TYPES type=MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING/*MYSQL_TYPE_VAR_STRING*/, my_bool* pind=null)
	{
		_ptr	= buffer.ptr;
		_len	= len;
		_type	= type;
		_pind	= pind;
		_plen	= null;
	}

	this(SqlString str)
	{
		_ptr	= str.str().ptr;
		_len	= str.blen();//alen();
		_type	= MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING;
		_pind	= str.ind().get_ref();
		_plen	= &str._len;
	}

	this(SqlNumber num)
	{
		_ptr	= num.get_ref();
		_len	= double.sizeof;
		_type	= MYSQL_FIELD_TYPES.MYSQL_TYPE_DOUBLE;
		_pind	= num.ind().get_ref();
		_plen	= null;
	}

  version(MYSQLPL_PREP_STMT) {
	this(SqlDate date)
	{
		_ptr	= &date._value;
		_len	= date._value.sizeof;
		_type	= date.get_data_type();
		_pind	= date.ind().get_ref();
		_plen	= null;
	}
  }

	this(MSqlBlob blob)
	{
		_ptr	= blob.get_ref();
		_len	= blob.get_len();
		_type	= MYSQL_FIELD_TYPES.MYSQL_TYPE_BLOB;
		_pind	= blob.ind().get_ref();
		_plen	= cast(uint*)blob.get_len_ref();
	}
};


class SqlVariant
{
	this()
	{
		_data_type = cast(MYSQL_FIELD_TYPES)-1;
	}

	this(MYSQL_FIELD_TYPES data_type)
	{
		_data_type = data_type;

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

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING:
			_string = new SqlString;
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_TINY:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_SHORT:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_INT24:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_YEAR:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_ENUM:
			_data_type = MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG;
			// fall through
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG:
			_int = new SqlInt;
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONGLONG:
			_integer = new SqlInteger;
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DECIMAL:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_NEWDECIMAL:	// MYSQL_VERSION_ID>=50000
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_FLOAT:
			_data_type = MYSQL_FIELD_TYPES.MYSQL_TYPE_DOUBLE;
			// fall through

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DOUBLE:
			_number = new SqlNumber;
			break;

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

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_NEWDATE:	// MYSQL_VERSION_ID>=50000
			_date = new SqlDate(MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_DATE);
			break;

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

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

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_TINY_BLOB:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_MEDIUM_BLOB:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG_BLOB:
			_data_type = MYSQL_FIELD_TYPES.MYSQL_TYPE_BLOB;

			// fall through
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_BLOB:
			_blob = new MSqlBlob;
			break;

		  default:
			throw new MSqlException("unsupported datatype");
		}
	}

	this(in SqlVariant other)
	{
		_data_type = cast(MYSQL_FIELD_TYPES)-1;

		assign(other);
	}

	~this()
	{
		clear();
	}

	this(in SqlString x)
	{
		_string = new SqlString(x);
		_data_type = MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING;
	}

	this(in char[] x)
	{
		_string = new SqlString(x);
		_data_type = MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING;
	}

	this(in SqlInt x)
	{
		_int = new SqlInt(x);
		_data_type = MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG;
	}

	this(in SqlInteger x)
	{
		_integer = new SqlInteger(x);
		_data_type = MYSQL_FIELD_TYPES.MYSQL_TYPE_LONGLONG;
	}

	this(in SqlNumber x)
	{
		_number = new SqlNumber(cast(double)x);
		_data_type = MYSQL_FIELD_TYPES.MYSQL_TYPE_DOUBLE;
	}

  version(MYSQLPL_PREP_STMT) {
	this(in SqlDate x)
	{
		_date = new SqlDate(cast(MYSQL_TIME)x);
		_data_type = x.get_data_type();
	}
  }

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

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING:
			delete _string;
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG:
			delete _int;
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONGLONG:
			delete _integer;
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DOUBLE:
			delete _number;
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATE:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_NEWDATE:	// MYSQL_VERSION_ID>=50000
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATETIME:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_TIME:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_TIMESTAMP:
			delete _date;
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_BLOB:
			delete _blob;
			break;

		  default:
			assert(0);
		}

		_data_type = cast(MYSQL_FIELD_TYPES)-1;
	}

	void assign(in SqlVariant other)
	{
		clear();

		_data_type = other._data_type;

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

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING:
			_string = new SqlString(other._string);
			break;

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

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

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DOUBLE:
			_number = new SqlNumber(cast(double)other._number);
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATE:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_NEWDATE:	// MYSQL_VERSION_ID>=50000
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATETIME:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_TIME:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_TIMESTAMP:
			_date = new SqlDate(other._date._value);
			break;

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

		  default:
			assert(0);
		}
	}

	int get_data_type()
	{
		return _data_type;
	}

	char[] str()
	{
		switch(_data_type) {
		  case cast(MYSQL_FIELD_TYPES)-1:
			return "";

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING:
			return _string.c_str();
	//		return *_string;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG:
			return _int.str();

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONGLONG:
			return _integer.str();

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DOUBLE:
			return _number.str();

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATE:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_NEWDATE:	// MYSQL_VERSION_ID>=50000
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATETIME:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_TIME:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_TIMESTAMP:
			return _date.str();

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_BLOB:
			return _blob.str();
			break;

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

	int get_int()
	{
		switch(_data_type) {
		  case cast(MYSQL_FIELD_TYPES)-1:
			return 0;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING:
			return std.c.stdlib.atoi(toStringz(_string.str()));

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG:
			return cast(int)_int;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONGLONG:
			return cast(LONGLONG)_integer;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DOUBLE:
			return _number.get_int();

		//case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATETIME:
		// 	return ...

		  default:
			throw new MSqlException("no conversion to int");
		}
	}

	long get_int64()
	{
		switch(_data_type) {
		  case cast(MYSQL_FIELD_TYPES)-1:
			return 0;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING:
			return std.c.stdlib.atoll(toStringz(_string.str()));

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG:
			return cast(int)_int;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONGLONG:
			return cast(LONGLONG)_integer;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DOUBLE:
			return _number.get_int64();

		//case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATETIME:
		// 	return ...

		  default:
			throw new MSqlException("no conversion to int64");
		}
	}

	double get_double()
	{
		switch(_data_type) {
		  case cast(MYSQL_FIELD_TYPES)-1:
			return 0;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING:
			return std.c.stdlib.atof(toStringz(_string.str()));

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG:
			return cast(int)_int;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONGLONG:
			return cast(LONGLONG)_integer;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DOUBLE:
			return _number.get_double();

		//case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATETIME:
		// 	return ...

		  default:
			throw new MSqlException("no conversion to double");
		}
	}

	SqlNumber number()
	{
		switch(_data_type) {
		  case cast(MYSQL_FIELD_TYPES)-1:
			return new SqlNumber();

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING:
			return new SqlNumber(_string.str());

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG:
			return new SqlNumber(cast(int)_int);

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONGLONG:
			return new SqlNumber(cast(LONGLONG)_integer);

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DOUBLE:
			return _number;

		//case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATETIME:
		// 	return ...

		  default:
			throw new MSqlException("no conversion to number");
		}
	}

	bool is_null()
	{
		switch(_data_type) {
		  case cast(MYSQL_FIELD_TYPES)-1:
			return true;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING:
			return _string.is_null();

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG:
			return _int.is_null();

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONGLONG:
			return _integer.is_null();

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DOUBLE:
			return _number.is_null();

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATE:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_NEWDATE:	// MYSQL_VERSION_ID>=50000
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATETIME:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_TIME:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_TIMESTAMP:
			return _date.is_null();

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_BLOB:
			return _blob.is_null();

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

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

  version(MYSQLPL_PREP_STMT) {
	void bind_result_column(SqlStatement stmt, MYSQL_BIND* bind)
	{
		switch(_data_type) {
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING:
			stmt.bind_result_column(new BindResultPar(_string), bind);
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONG:
			stmt.bind_result_column(new BindResultPar(_int), bind);
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_LONGLONG:
			stmt.bind_result_column(new BindResultPar(_integer), bind);
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DOUBLE:
			stmt.bind_result_column(new BindResultPar(_number), bind);
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATE:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_NEWDATE:	// MYSQL_VERSION_ID>=50000
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATETIME:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_TIME:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_TIMESTAMP:
			stmt.bind_result_column(new BindResultPar(_date), bind);
			break;

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_BLOB:
			stmt.bind_result_column(new BindResultPar(_blob), bind);
			break;

		  default:
			assert(0);
		}
	}
  }

  version(MYSQLPL_PREP_STMT) {
	SqlDate date(MYSQL_TIMESTAMP_TYPE type=MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_DATE)
	{
		switch(_data_type) {
		  case cast(MYSQL_FIELD_TYPES)-1:
			return new SqlDate();

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_STRING:
			return new SqlDate(_string.str(), MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_DATE);

		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATE:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_NEWDATE:	// MYSQL_VERSION_ID>=50000
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_DATETIME:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_TIME:
		  case MYSQL_FIELD_TYPES.MYSQL_TYPE_TIMESTAMP:
	 		return new SqlDate(_date, type);

		  default:
			throw new MSqlException("no conversion to date type");
		}
	}

	SqlDate datetime()
	{
		return date(MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_DATETIME);
	}

	SqlDate time()
	{
		return date(MYSQL_TIMESTAMP_TYPE.MYSQL_TIMESTAMP_TIME);
	}
  }

protected:
	union {
		SqlString	_string;
		SqlInt		_int;
		SqlInteger	_integer;
		SqlNumber	_number;
	  version(MYSQLPL_PREP_STMT) {
		SqlDate		_date;
	  }
		MSqlBlob	_blob;
	};

	MYSQL_FIELD_TYPES _data_type;
};

}
