///////////////////////////////////////////////////////////////////////////////
//                                                                             
//  Copyright (C) 2010-2011  Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>     
//                                                                             
//  Distributed under:
//
//                   the Boost Software License, Version 1.0.
//              (See accompanying file LICENSE_1_0.txt or copy at 
//                     http://www.boost.org/LICENSE_1_0.txt)
//
//  or (at your opinion) under:
//
//                               The MIT License
//                 (See accompanying file MIT.txt or a copy at
//              http://www.opensource.org/licenses/mit-license.php)
//
///////////////////////////////////////////////////////////////////////////////
#define CPPDB_DRIVER_SOURCE
#ifdef CPPDB_WITH_MYSQL
# define CPPDB_SOURCE
#endif
#include <mysql.h>

#include <cppdb/backend.h>
#include <cppdb/errors.h>
#include <cppdb/utils.h>
#include <cppdb/numeric_util.h>
#include <sstream>
#include <vector>
#include <limits>
#include <iomanip>
#include <stdlib.h>
#include <string.h>

#include <iostream>

namespace cppdb {

namespace mysql_backend {	

class cppdb_myerror : public cppdb_error 
{
public:
	cppdb_myerror(std::string const &str) : 
		cppdb_error("cppdb::mysql::" + str)
	{
	}
};
namespace unprep {
	class result : public backend::result {
	public:

		///
		/// Check if the next row in the result exists. If the DB engine can't perform
		/// this check without loosing data for current row, it should return next_row_unknown.
		///
		virtual next_row has_next() 
		{
			if(!res_)
				return last_row_reached;
			if(current_row_ >= mysql_num_rows(res_))
				return last_row_reached;
			else
				return next_row_exists;
		}
		///
		/// Move to next row. Should be called before first access to any of members. If no rows remain
		/// return false, otherwise return true
		///
		virtual bool next() 
		{
			if(!res_)
				return false;
			current_row_ ++;
			row_ = mysql_fetch_row(res_);
			if(!row_)
				return false;
			return true;
		}
		///
		/// Fetch an integer value for column \a col starting from 0.
		///
		/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
		/// can't be converted to integer or its range is not supported by the integer type.
		///
		char const *at(int col)
		{
			if(!res_)
				throw empty_row_access();
			if(col < 0 || col >= cols_)
				throw invalid_column();
			return row_[col];
		}

		char const *at(int col,size_t &len)
		{
			if(!res_)
				throw empty_row_access();
			if(col < 0 || col >= cols_)
				throw invalid_column();
			unsigned long *lengths = mysql_fetch_lengths(res_);
			if(lengths==0) 
				throw cppdb_myerror("Can't get length of column");
			len = lengths[col];
			return row_[col];
		}

		template<typename T>
		bool do_fetch(int col,T &v)
		{
			size_t len;
			char const *s=at(col,len);
			if(!s)
				return false;
			v = parse_number<T>(std::string(s,len),fmt_);
			return true;
		}
		virtual bool fetch(int col,short &v) 
		{
			return do_fetch(col,v);;
		}
		virtual bool fetch(int col,unsigned short &v) 
		{
			return do_fetch(col,v);
		}
		virtual bool fetch(int col,int &v)
		{
			return do_fetch(col,v);
		}
		virtual bool fetch(int col,unsigned &v) 
		{
			return do_fetch(col,v);
		}
		virtual bool fetch(int col,long &v)
		{
			return do_fetch(col,v);
		}
		virtual bool fetch(int col,unsigned long &v)
		{
			return do_fetch(col,v);
		}
		virtual bool fetch(int col,long long &v)
		{
			return do_fetch(col,v);
		}
		///
		virtual bool fetch(int col,unsigned long long &v)
		{
			return do_fetch(col,v);
		}
		virtual bool fetch(int col,float &v)
		{
			return do_fetch(col,v);
		}
		virtual bool fetch(int col,double &v)
		{
			return do_fetch(col,v);
		}
		virtual bool fetch(int col,long double &v)
		{
			return do_fetch(col,v);
		}
		///
		virtual bool fetch(int col,std::string &v)
		{
			size_t len;
			char const *s=at(col,len);
			if(!s)
				return false;
			v.assign(s,len);
			return true;
		}
		virtual bool fetch(int col,std::ostream &v)
		{
			size_t len;
			char const *s=at(col,len);
			if(!s)
				return false;
			v.write(s,len);
			return true;
		}
		///
		/// Fetch a date-time value for column \a col starting from 0.
		/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
		///
		/// Should throw invalid_column() \a col value is invalid. If the data can't be converted
		/// to date-time it should throw bad_value_cast()
		///
		virtual bool fetch(int col,std::tm &v) 
		{
			size_t len;
			char const *s=at(col,len);
			if(!s)
				return false;
			std::string tmp(s,len);
			v = parse_time(tmp);
			return true;
		}
		///
		/// Check if the column \a col is NULL starting from 0, should throw invalid_column() if the index out of range
		///
		virtual bool is_null(int col) 
		{
			return at(col) == 0;
		}
		///
		/// Return the number of columns in the result. Should be valid even without calling next() first time.
		///
		virtual int cols() 
		{
			return cols_;
		}
		virtual std::string column_to_name(int col) 
		{
			if(col < 0 || col >=cols_)
				throw invalid_column();
			if(!res_)
				throw empty_row_access();
			MYSQL_FIELD *flds=mysql_fetch_fields(res_);
			if(!flds) {
				throw cppdb_myerror("Internal error empty fileds");
			}
			return flds[col].name;
		}
		virtual int name_to_column(std::string const &name) 
		{
			if(!res_)
				throw empty_row_access();
			MYSQL_FIELD *flds=mysql_fetch_fields(res_);
			if(!flds) {
				throw cppdb_myerror("Internal error empty fileds");
			}
			for(int i=0;i<cols_;i++)
				if(name == flds[i].name)
					return i;
			return -1;
		}

		// End of API
		
		result(MYSQL *conn) : 
			res_(0),
			cols_(0),
			current_row_(0),
			row_(0)
		{
			fmt_.imbue(std::locale::classic());
			res_ = mysql_store_result(conn);
			if(!res_) {
				cols_ = mysql_field_count(conn);
				if(cols_ == 0)
					throw cppdb_myerror("Seems that the query does not produce any result");
			}
			else {
				cols_ = mysql_num_fields(res_);
			}

		}
		~result()
		{
			if(res_)
				mysql_free_result(res_);
		}
	private:
		std::istringstream fmt_;
		MYSQL_RES *res_;
		int cols_;
		unsigned current_row_;
		MYSQL_ROW row_;
	};
	
	class statement : public backend::statement {
	public:
		virtual std::string const &sql_query() 
		{
			return query_;
		}

		virtual void bind(int col,std::string const &s)
		{
			bind(col,s.c_str(),s.c_str()+s.size());
		}
		virtual void bind(int col,char const *s)
		{
			bind(col,s,s+strlen(s));
		}
		virtual void bind(int col,char const *b,char const *e) 
		{
			std::vector<char> buf(2*(e-b)+1);
			size_t len = mysql_real_escape_string(conn_,&buf.front(),b,e-b);
			std::string &s=at(col);
			s.clear();
			s.reserve(e-b+2);
			s+='\'';
			s.append(&buf.front(),len);
			s+='\'';
		}
		virtual void bind(int col,std::tm const &v) 
		{
			std::string &s = at(col);
			s.clear();
			s.reserve(30);
			s+='\'';
			s+=cppdb::format_time(v);
			s+='\'';
		}
		virtual void bind(int col,std::istream &v)
		{
			std::ostringstream ss;
			ss << v.rdbuf();
			std::string tmp=ss.str();
			bind(col,tmp);
		}
		template<typename T>
		void do_bind(int col,T v)
		{
			fmt_.str(std::string());
			if(!std::numeric_limits<T>::is_integer)
				fmt_ << std::setprecision(std::numeric_limits<T>::digits10+1);
			fmt_ << v;
			std::string tmp = fmt_.str();
			at(col).swap(tmp);
		}
		virtual void bind(int col,int v)
		{
			do_bind(col,v);
		}
		virtual void bind(int col,unsigned v)
		{
			do_bind(col,v);
		}
		virtual void bind(int col,long v)
		{
			do_bind(col,v);
		}
		virtual void bind(int col,unsigned long v)
		{
			do_bind(col,v);
		}
		virtual void bind(int col,long long v)
		{
			do_bind(col,v);
		}
		virtual void bind(int col,unsigned long long v)
		{
			do_bind(col,v);
		}
		virtual void bind(int col,double v) 
		{
			do_bind(col,v);
		}
		virtual void bind(int col,long double v)
		{
			do_bind(col,v);
		}
		virtual void bind_null(int col)
		{
			at(col)="NULL";
		}
		virtual long long sequence_last(std::string const &/*sequence*/) 
		{
			return mysql_insert_id(conn_);
		}
		virtual unsigned long long affected()
		{
			return mysql_affected_rows(conn_);
		}
		
		void bind_all(std::string &real_query)
		{
			size_t total = query_.size();
			for(unsigned i=0;i<params_.size();i++) {
				total+=params_[i].size();
			}
			real_query.clear();
			real_query.reserve(total);
			size_t pos_ = 0;
			for(unsigned i=0;i<params_.size();i++) {
				size_t marker = binders_[i];
				real_query.append(query_,pos_,marker-pos_);
				pos_ = marker+1;
				real_query.append(params_[i]);
			}
			real_query.append(query_,pos_,std::string::npos);
		}

		virtual result *query() 
		{
			std::string real_query;	
			bind_all(real_query);
			reset_params();
			if(mysql_real_query(conn_,real_query.c_str(),real_query.size())) {
				throw cppdb_myerror(mysql_error(conn_));
			}
			return new result(conn_);
		}
		
		virtual void exec() 
		{
			std::string real_query;	
			bind_all(real_query);
			reset_params();
			if(mysql_real_query(conn_,real_query.c_str(),real_query.size())) {
				throw cppdb_myerror(mysql_error(conn_));
			}
			MYSQL_RES *r=mysql_store_result(conn_);
			if(r){
				mysql_free_result(r);
				throw cppdb_myerror("Calling exec() on query!");
			}
		}

		std::string &at(int col)
		{
			if(col < 1 || col > params_no_) {
				throw invalid_placeholder();
			}
			return params_[col-1];
		}
		void reset_params()
		{
			params_.clear();
			params_.resize(params_no_,"NULL");
		}
		
		statement(std::string const &q,MYSQL *conn) :
			query_(q),
			conn_(conn),
			params_no_(0)
		{
			fmt_.imbue(std::locale::classic());
			bool inside_text = false;
			for(size_t i=0;i<query_.size();i++) {
				if(query_[i]=='\'') {
					inside_text=!inside_text;
				}
				if(query_[i]=='?' && !inside_text) {
					params_no_++;
					binders_.push_back(i);
				}
			}
			if(inside_text) {
				throw cppdb_myerror("Unterminated string found in query");
			}
			reset_params();
		}
		virtual ~statement()
		{
		}
		virtual void reset()
		{
		}

	private:
		std::ostringstream fmt_;
		std::vector<std::string> params_;
		std::vector<size_t> binders_;

		std::string query_;
		MYSQL *conn_;
		int params_no_;
	};
} // uprep

namespace prep {

	class result : public backend::result {
		struct bind_data {
			bind_data() :
				ptr(0),
				length(0),
				is_null(0),
				error(0)
			{
				memset(&buf,0,sizeof(buf));
			}
			char buf[128];
			std::vector<char> vbuf;
			char *ptr;
			unsigned long length;
			my_bool is_null;
			my_bool error;
		};
	public:

		///
		/// Check if the next row in the result exists. If the DB engine can't perform
		/// this check without loosing data for current row, it should return next_row_unknown.
		///
		virtual next_row has_next() 
		{
			if(current_row_ >= mysql_stmt_num_rows(stmt_))
				return last_row_reached;
			else
				return next_row_exists;
		}
		///
		/// Move to next row. Should be called before first access to any of members. If no rows remain
		/// return false, otherwise return true
		///
		virtual bool next() 
		{
			current_row_ ++;
			reset();
			if(cols_ > 0) {
				if(mysql_stmt_bind_result(stmt_,&bind_[0])) {
					throw cppdb_myerror(mysql_stmt_error(stmt_));
				}
			}
			int r = mysql_stmt_fetch(stmt_);
			if(r==MYSQL_NO_DATA) { 
				return false;
			}
			if(r==MYSQL_DATA_TRUNCATED) {
				for(int i=0;i<cols_;i++) {
					if(bind_data_[i].error && !bind_data_[i].is_null && bind_data_[i].length >= sizeof(bind_data_[i].buf)) {
						bind_data_[i].vbuf.resize(bind_data_[i].length);
						bind_[i].buffer = &bind_data_[i].vbuf.front();
						bind_[i].buffer_length = bind_data_[i].length;
						if(mysql_stmt_fetch_column(stmt_,&bind_[i],i,0)) {
							throw cppdb_myerror(mysql_stmt_error(stmt_));
						}
						bind_data_[i].ptr = &bind_data_[i].vbuf.front();
					}
				}
			}
			return true;
		}
		///
		/// Fetch an integer value for column \a col starting from 0.
		///
		/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
		/// can't be converted to integer or its range is not supported by the integer type.
		///
		bind_data &at(int col)
		{
			if(col < 0 || col >= cols_)
				throw invalid_column();
			if(bind_data_.empty())
				throw cppdb_myerror("Attempt to access data without fetching it first");
			return bind_data_.at(col);
		}

		template<typename T>
		bool do_fetch(int col,T &v)
		{
			bind_data &d=at(col);
			if(d.is_null)
				return false;

			v=parse_number<T>(std::string(d.ptr,d.length),fmt_);
			return true;
		}
		virtual bool fetch(int col,short &v) 
		{
			return do_fetch(col,v);;
		}
		///
		/// Fetch an integer value for column \a col starting from 0.
		/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
		///
		/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
		/// can't be converted to integer or its range is not supported by the integer type.
		///
		virtual bool fetch(int col,unsigned short &v) 
		{
			return do_fetch(col,v);
		}
		///
		/// Fetch an integer value for column \a col starting from 0.
		/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
		///
		/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
		/// can't be converted to integer or its range is not supported by the integer type.
		///
		virtual bool fetch(int col,int &v)
		{
			return do_fetch(col,v);
		}
		///
		/// Fetch an integer value for column \a col starting from 0.
		/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
		///
		/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
		/// can't be converted to integer or its range is not supported by the integer type.
		///
		virtual bool fetch(int col,unsigned &v) 
		{
			return do_fetch(col,v);
		}
		///
		/// Fetch an integer value for column \a col starting from 0.
		/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
		///
		/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
		/// can't be converted to integer or its range is not supported by the integer type.
		///
		virtual bool fetch(int col,long &v)
		{
			return do_fetch(col,v);
		}
		///
		/// Fetch an integer value for column \a col starting from 0.
		/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
		///
		/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
		/// can't be converted to integer or its range is not supported by the integer type.
		///
		virtual bool fetch(int col,unsigned long &v)
		{
			return do_fetch(col,v);
		}
		///
		/// Fetch an integer value for column \a col starting from 0.
		/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
		///
		/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
		/// can't be converted to integer or its range is not supported by the integer type.
		///
		virtual bool fetch(int col,long long &v)
		{
			return do_fetch(col,v);
		}
		///
		/// Fetch an integer value for column \a col starting from 0.
		/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
		///
		/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
		/// can't be converted to integer or its range is not supported by the integer type.
		///
		virtual bool fetch(int col,unsigned long long &v)
		{
			return do_fetch(col,v);
		}
		///
		/// Fetch a floating point value for column \a col starting from 0.
		/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
		///
		/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
		/// can't be converted to floating point value.
		///
		virtual bool fetch(int col,float &v)
		{
			return do_fetch(col,v);
		}
		///
		/// Fetch a floating point value for column \a col starting from 0.
		/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
		///
		/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
		/// can't be converted to floating point value.
		///
		virtual bool fetch(int col,double &v)
		{
			return do_fetch(col,v);
		}
		///
		/// Fetch a floating point value for column \a col starting from 0.
		/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
		///
		/// Should throw invalid_column() \a col value is invalid, should throw bad_value_cast() if the underlying data
		/// can't be converted to floating point value.
		///
		virtual bool fetch(int col,long double &v)
		{
			return do_fetch(col,v);
		}
		///
		/// Fetch a string value for column \a col starting from 0.
		/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
		///
		/// Should throw invalid_column() \a col value is invalid, any data should be convertible to
		/// text value (as formatting integer, floating point value or date-time as string).
		///
		virtual bool fetch(int col,std::string &v)
		{
			bind_data &d=at(col);
			if(d.is_null)
				return false;
			v.assign(d.ptr,d.length);
			return true;
		}
		///
		/// Fetch a BLOB value for column \a col starting from 0.
		/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
		///
		/// Should throw invalid_column() \a col value is invalid, any data should be convertible to
		/// BLOB value as text (as formatting integer, floating point value or date-time as string).
		///
		virtual bool fetch(int col,std::ostream &v)
		{
			bind_data &d=at(col);
			if(d.is_null)
				return false;
			v.write(d.ptr,d.length);
			return true;
		}
		///
		/// Fetch a date-time value for column \a col starting from 0.
		/// Returns true if ok, returns false if the column value is NULL and the referenced object should remain unchanged
		///
		/// Should throw invalid_column() \a col value is invalid. If the data can't be converted
		/// to date-time it should throw bad_value_cast()
		///
		virtual bool fetch(int col,std::tm &v) 
		{
			std::string tmp;
			if(!fetch(col,tmp))
				return false;
			v = parse_time(tmp);
			return true;
		}
		///
		/// Check if the column \a col is NULL starting from 0, should throw invalid_column() if the index out of range
		///
		virtual bool is_null(int col) 
		{
			return at(col).is_null;
		}
		///
		/// Return the number of columns in the result. Should be valid even without calling next() first time.
		///
		virtual int cols() 
		{
			return cols_;
		}
		virtual std::string column_to_name(int col) 
		{
			if(col < 0 || col >=cols_)
				throw invalid_column();
			MYSQL_FIELD *flds=mysql_fetch_fields(meta_);
			if(!flds) {
				throw cppdb_myerror("Internal error empty fileds");
			}
			return flds[col].name;
		}
		virtual int name_to_column(std::string const &name) 
		{
			MYSQL_FIELD *flds=mysql_fetch_fields(meta_);
			if(!flds) {
				throw cppdb_myerror("Internal error empty fileds");
			}
			for(int i=0;i<cols_;i++)
				if(name == flds[i].name)
					return i;
			return -1;
		}

		// End of API
		
		result(MYSQL_STMT *stmt) : 
			stmt_(stmt), current_row_(0),meta_(0)
		{
			fmt_.imbue(std::locale::classic());
			cols_ = mysql_stmt_field_count(stmt_);
			if(mysql_stmt_store_result(stmt_)) {
				throw cppdb_myerror(mysql_stmt_error(stmt_));
			}
			meta_ = mysql_stmt_result_metadata(stmt_);
			if(!meta_) {
				throw cppdb_myerror("Seems that the query does not produce any result");
			}
		}
		~result()
		{
			mysql_free_result(meta_);
		}
		void reset()
		{
			bind_.resize(0);
			bind_data_.resize(0);
			bind_.resize(cols_,MYSQL_BIND());
			bind_data_.resize(cols_,bind_data());
			for(int i=0;i<cols_;i++) {
				bind_[i].buffer_type = MYSQL_TYPE_STRING;
				bind_[i].buffer = bind_data_[i].buf;
				bind_[i].buffer_length = sizeof(bind_data_[i].buf);
				bind_[i].length = &bind_data_[i].length;
				bind_[i].is_null = &bind_data_[i].is_null;
				bind_[i].error = &bind_data_[i].error;
				bind_data_[i].ptr = bind_data_[i].buf;
			}
		}
	private:
		std::istringstream fmt_;
		int cols_;
		MYSQL_STMT *stmt_;
		unsigned current_row_;
		MYSQL_RES *meta_;
		std::vector<MYSQL_BIND> bind_;
		std::vector<bind_data> bind_data_;
	};

	class statement : public backend::statement {
		struct param {
			my_bool is_null;
			bool is_blob;
			unsigned long length;
			std::string value;
			void *buffer;
			param() : 
				is_null(1),
				is_blob(false),
				length(0),
				buffer(0)
			{
			}
			void set(char const *b,char const *e,bool blob=false)
			{
				length = e - b;
				buffer = const_cast<char *>(b);
				is_blob = blob;
				is_null = 0;
			}
			void set_str(std::string const &s)
			{
				value = s;
				buffer = const_cast<char *>(value.c_str());
				length = value.size();
				is_null = 0;
			}
			void set(std::tm const &t)
			{
				set_str(cppdb::format_time(t));
			}
			void bind_it(MYSQL_BIND *b) 
			{
				b->is_null = &is_null;
				if(!is_null) {
					b->buffer_type = is_blob ? MYSQL_TYPE_BLOB : MYSQL_TYPE_STRING;
					b->buffer = buffer;
					b->buffer_length = length;
					b->length = &length;
				}
				else {
					b->buffer_type = MYSQL_TYPE_NULL;
				}
			}
		};

	public:
		// Begin of API
		///
		/// Get the query the statement works with. Return it as is, used as key for statement
		/// caching 
		///
		virtual std::string const &sql_query() 
		{
			return query_;
		}

		///
		/// Bind a text value to column \a col (starting from 1). You may assume
		/// that the reference remains valid until real call of query() or exec()
		///
		/// Should throw invalid_placeholder() if the value of col is out of range. May
		/// ignore if it is impossible to know whether the placeholder exists without special
		/// support from back-end.
		///
		virtual void bind(int col,std::string const &s)
		{
			at(col).set(s.c_str(),s.c_str()+s.size());
		}
		///
		/// Bind a text value to column \a col (starting from 1). You may assume
		/// that the reference remains valid until real call of query() or exec()
		///
		/// Should throw invalid_placeholder() if the value of col is out of range. May
		/// ignore if it is impossible to know whether the placeholder exists without special
		/// support from back-end.
		///
		virtual void bind(int col,char const *s)
		{
			at(col).set(s,s+strlen(s));
		}
		///
		/// Bind a text value to column \a col (starting from 1). You may assume
		/// that the reference remains valid until real call of query() or exec()
		///
		/// Should throw invalid_placeholder() if the value of col is out of range. May
		/// ignore if it is impossible to know whether the placeholder exists without special
		/// support from back-end.
		///
		virtual void bind(int col,char const *b,char const *e) 
		{
			at(col).set(b,e);
		}
		///
		/// Bind a date-time value to column \a col (starting from 1).
		///
		/// Should throw invalid_placeholder() if the value of col is out of range. May
		/// ignore if it is impossible to know whether the placeholder exists without special
		/// support from back-end.
		///
		virtual void bind(int col,std::tm const &v) 
		{
			at(col).set(v);
		}
		///
		/// Bind a BLOB value to column \a col (starting from 1).
		///
		/// Should throw invalid_placeholder() if the value of col is out of range. May
		/// ignore if it is impossible to know whether the placeholder exists without special
		/// support from back-end.
		///
		virtual void bind(int col,std::istream &v)
		{
			std::ostringstream ss;
			ss << v.rdbuf();
			at(col).set_str(ss.str());
			at(col).is_blob = true;
		}
		template<typename T>
		void do_bind(int col,T v)
		{
			fmt_.str(std::string());
			if(!std::numeric_limits<T>::is_integer)
				fmt_ << std::setprecision(std::numeric_limits<T>::digits10+1);
			fmt_ << v;
			at(col).set_str(fmt_.str());
		}
		///
		/// Bind an integer value to column \a col (starting from 1).
		///
		/// Should throw invalid_placeholder() if the value of col is out of range. May
		/// ignore if it is impossible to know whether the placeholder exists without special
		/// support from back-end.
		///
		virtual void bind(int col,int v)
		{
			do_bind(col,v);
		}
		///
		/// Bind an integer value to column \a col (starting from 1).
		///
		/// Should throw invalid_placeholder() if the value of col is out of range. May
		/// ignore if it is impossible to know whether the placeholder exists without special
		/// support from back-end.
		///
		/// May throw bad_value_cast() if the value out of supported range by the DB. 
		///
		virtual void bind(int col,unsigned v)
		{
			do_bind(col,v);
		}
		///
		/// Bind an integer value to column \a col (starting from 1).
		///
		/// Should throw invalid_placeholder() if the value of col is out of range. May
		/// ignore if it is impossible to know whether the placeholder exists without special
		/// support from back-end.
		///
		/// May throw bad_value_cast() if the value out of supported range by the DB. 
		///
		virtual void bind(int col,long v)
		{
			do_bind(col,v);
		}
		///
		/// Bind an integer value to column \a col (starting from 1).
		///
		/// Should throw invalid_placeholder() if the value of col is out of range. May
		/// ignore if it is impossible to know whether the placeholder exists without special
		/// support from back-end.
		///
		/// May throw bad_value_cast() if the value out of supported range by the DB. 
		///
		virtual void bind(int col,unsigned long v)
		{
			do_bind(col,v);
		}
		///
		/// Bind an integer value to column \a col (starting from 1).
		///
		/// Should throw invalid_placeholder() if the value of col is out of range. May
		/// ignore if it is impossible to know whether the placeholder exists without special
		/// support from back-end.
		///
		/// May throw bad_value_cast() if the value out of supported range by the DB. 
		///
		virtual void bind(int col,long long v)
		{
			do_bind(col,v);
		}
		///
		/// Bind an integer value to column \a col (starting from 1).
		///
		/// Should throw invalid_placeholder() if the value of col is out of range. May
		/// ignore if it is impossible to know whether the placeholder exists without special
		/// support from back-end.
		///
		/// May throw bad_value_cast() if the value out of supported range by the DB. 
		///
		virtual void bind(int col,unsigned long long v)
		{
			do_bind(col,v);
		}
		///
		/// Bind a floating point value to column \a col (starting from 1).
		///
		/// Should throw invalid_placeholder() if the value of col is out of range. May
		/// ignore if it is impossible to know whether the placeholder exists without special
		/// support from back-end.
		///
		virtual void bind(int col,double v) 
		{
			do_bind(col,v);
		}
		///
		/// Bind a floating point value to column \a col (starting from 1).
		///
		/// Should throw invalid_placeholder() if the value of col is out of range. May
		/// ignore if it is impossible to know whether the placeholder exists without special
		/// support from back-end.
		///
		virtual void bind(int col,long double v)
		{
			do_bind(col,v);
		}
		///
		/// Bind a NULL value to column \a col (starting from 1).
		///
		/// Should throw invalid_placeholder() if the value of col is out of range. May
		/// ignore if it is impossible to know whether the placeholder exists without special
		/// support from back-end.
		///
		virtual void bind_null(int col)
		{
			at(col)=param();
		}
		///
		/// Fetch the last sequence generated for last inserted row. May use sequence as parameter
		/// if the database uses sequences, should ignore the parameter \a sequence if the last
		/// id is fetched without parameter.
		///
		/// Should be called after exec() for insert statement, otherwise the behavior is undefined.
		///
		/// MUST throw not_supported_by_backend() if such option is not supported by the DB engine.
		///
		virtual long long sequence_last(std::string const &/*sequence*/) 
		{
			return mysql_stmt_insert_id(stmt_);
		}
		///
		/// Return the number of affected rows by last statement.
		///
		/// Should be called after exec(), otherwise behavior is undefined.
		///
		virtual unsigned long long affected()
		{
			return mysql_stmt_affected_rows(stmt_);
		}
		
		void bind_all()
		{
			if(!params_.empty()) {
				for(unsigned i=0;i<params_.size();i++)
					params_[i].bind_it(&bind_[i]);
				if(mysql_stmt_bind_param(stmt_,&bind_.front())) {
					throw cppdb_myerror(mysql_stmt_error(stmt_));
				}
			}
		}

		///
		/// Return SQL Query result, MAY throw cppdb_error if the statement is not a query
		///
		virtual result *query() 
		{
			bind_all();
			if(mysql_stmt_execute(stmt_)) {
				throw cppdb_myerror(mysql_stmt_error(stmt_));
			}
			return new result(stmt_);
		}
		///
		/// Execute a statement, MAY throw cppdb_error if the statement returns results.
		///
		virtual void exec() 
		{
			bind_all();
			if(mysql_stmt_execute(stmt_)) {
				throw cppdb_myerror(mysql_stmt_error(stmt_));
			}
			if(mysql_stmt_store_result(stmt_)) {
				throw cppdb_myerror(mysql_stmt_error(stmt_));
			}
			MYSQL_RES *r=mysql_stmt_result_metadata(stmt_);
			if(r) {
				mysql_free_result(r);
				throw cppdb_myerror("Calling exec() on query!");
			}
		}
		// End of API

		// Caching support
		
		statement(std::string const &q,MYSQL *conn) :
			query_(q),
			stmt_(0),
			params_count_(0)
		{
			fmt_.imbue(std::locale::classic());

			stmt_ = mysql_stmt_init(conn);
			try {
				if(!stmt_) {
					throw cppdb_myerror(" Failed to create a statement");
				}
				if(mysql_stmt_prepare(stmt_,q.c_str(),q.size())) {
					throw cppdb_myerror(mysql_stmt_error(stmt_));
				}
				params_count_ = mysql_stmt_param_count(stmt_);
				reset_data();
			}
			catch(...) {
				if(stmt_)
					mysql_stmt_close(stmt_);
				throw;
			}
		}
		virtual ~statement()
		{
			mysql_stmt_close(stmt_);
		}
		void reset_data()
		{
			params_.resize(0);
			params_.resize(params_count_);
			bind_.resize(0);
			bind_.resize(params_count_,MYSQL_BIND());
		}
		virtual void reset()
		{
			reset_data();
			mysql_stmt_reset(stmt_);
		}

	private:
		param &at(int col)
		{
			if(col < 1 || col > params_count_)
				throw invalid_placeholder();
			return params_[col-1];
		}

		std::ostringstream fmt_;
		std::vector<param> params_;
		std::vector<MYSQL_BIND> bind_;
		std::string query_;
		MYSQL_STMT *stmt_;
		int params_count_;
	};

} // prep

class connection;

class connection : public backend::connection {
public:
	connection(connection_info const &ci) : 
		backend::connection(ci),
		conn_(0)
	{
		conn_ = mysql_init(0);
		if(!conn_) {
			throw cppdb_error("cppdb::mysql failed to create connection");
		}
		std::string host = ci.get("host","");
		char const *phost = host.empty() ? 0 : host.c_str();
		std::string user = ci.get("user","");
		char const *puser = user.empty() ? 0 : user.c_str();
		std::string password = ci.get("password","");
		char const *ppassword = password.empty() ? 0 : password.c_str();
		std::string database = ci.get("database","");
		char const *pdatabase = database.empty() ? 0 : database.c_str();
		int port = ci.get("port",0);
		std::string unix_socket = ci.get("unix_socket","");
		char const *punix_socket = unix_socket.empty() ? 0 : unix_socket.c_str();

#if MYSQL_VERSION_ID >= 50507
		std::string default_auth = ci.get("default_auth","");
		if (!default_auth.empty()) {
			mysql_set_option(MYSQL_DEFAULT_AUTH, default_auth.c_str());
		}
#endif
		std::string init_command = ci.get("init_command","");
		if(!init_command.empty()) {
			mysql_set_option(MYSQL_INIT_COMMAND, init_command.c_str());
		}
		if(ci.has("opt_compress")) {
			if(ci.get("opt_compress", 1)) {
				mysql_set_option(MYSQL_OPT_COMPRESS, NULL);
			}
		}
		if(ci.has("opt_connect_timeout")) {
			if(unsigned connect_timeout = ci.get("opt_connect_timeout", 0)) {
				mysql_set_option(MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
			}
		}
		if(ci.has("opt_guess_connection")) {
			if(ci.get("opt_guess_connection", 1)) {
				mysql_set_option(MYSQL_OPT_GUESS_CONNECTION, NULL);
			}
		}
		if(ci.has("opt_local_infile")) {
			if(unsigned local_infile = ci.get("opt_local_infile", 0)) {
				mysql_set_option(MYSQL_OPT_CONNECT_TIMEOUT, &local_infile);
			}
		}
		if(ci.has("opt_named_pipe")) {
			if(ci.get("opt_named_pipe", 1)) {
				mysql_set_option(MYSQL_OPT_NAMED_PIPE, NULL);
			}
		}
		if(ci.has("opt_protocol")) {
			if(unsigned protocol = ci.get("opt_protocol", 0)) {
				mysql_set_option(MYSQL_OPT_PROTOCOL, &protocol);
			}
		}
		if(ci.has("opt_read_timeout")) {
			if(unsigned read_timeout = ci.get("opt_read_timeout", 0)) {
				mysql_set_option(MYSQL_OPT_READ_TIMEOUT, &read_timeout);
			}
		}
		if(ci.has("opt_reconnect")) {
			if(unsigned reconnect = ci.get("opt_reconnect", 1)) {
				my_bool value = reconnect;
				mysql_set_option(MYSQL_OPT_RECONNECT, &value);
			}
		}
#if MYSQL_VERSION_ID >= 50507
		std::string plugin_dir = ci.get("plugin_dir", "");
		if(!plugin_dir.empty()) {
			mysql_set_option(MYSQL_PLUGIN_DIR, plugin_dir.c_str());
		}
#endif
		std::string set_client_ip = ci.get("set_client_ip", "");
		if(!set_client_ip.empty()) {
			mysql_set_option(MYSQL_SET_CLIENT_IP, set_client_ip.c_str());
		}
		if(ci.has("opt_ssl_verify_server_cert")) {
			if(unsigned verify = ci.get("opt_ssl_verify_server_cert", 1)) {
				my_bool value = verify;
				mysql_set_option(MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &value);
			}
		}
		if(ci.has("opt_use_embedded_connection")) {
			if(ci.get("opt_use_embedded_connection", 1)) {
				mysql_set_option(MYSQL_OPT_USE_EMBEDDED_CONNECTION, NULL);
			}
		}
		if(ci.has("opt_use_remote_connection")) {
			if(ci.get("opt_use_remote_connection", 1)) {
				mysql_set_option(MYSQL_OPT_USE_REMOTE_CONNECTION, NULL);
			}
		}
		if(ci.has("opt_write_timeout")) {
			if(unsigned write_timeout = ci.get("opt_write_timeout", 0)) {
				mysql_set_option(MYSQL_OPT_WRITE_TIMEOUT, &write_timeout);
			}
		}
		std::string read_default_file = ci.get("read_default_file", "");
		if(!read_default_file.empty()) {
			mysql_set_option(MYSQL_READ_DEFAULT_FILE, read_default_file.c_str());
		}
		std::string read_default_group = ci.get("read_default_group", "");
		if(!read_default_group.empty()) {
			mysql_set_option(MYSQL_READ_DEFAULT_GROUP, read_default_group.c_str());
		}
		if(ci.has("report_data_truncation")) {
			if(unsigned report = ci.get("report_data_truncation", 1)) {
				my_bool value = report;
				mysql_set_option(MYSQL_REPORT_DATA_TRUNCATION, &value);
			}
		}
#if MYSQL_VERSION_ID >= 40101
		if(ci.has("secure_auth")) {
			if(unsigned secure = ci.get("secure_auth", 1)) {
				my_bool value = secure;
				mysql_set_option(MYSQL_SECURE_AUTH, &value);
			}
		}
#endif
		std::string set_charset_dir = ci.get("set_charset_dir", "");
		if(!set_charset_dir.empty()) {
			mysql_set_option(MYSQL_SET_CHARSET_DIR, set_charset_dir.c_str());
		}
		std::string set_charset_name = ci.get("set_charset_name", "");
		if(!set_charset_name.empty()) {
			mysql_set_option(MYSQL_SET_CHARSET_NAME, set_charset_name.c_str());
		}
		std::string shared_memory_base_name = ci.get("shared_memory_base_name", "");
		if(!shared_memory_base_name.empty()) {
			mysql_set_option(MYSQL_SHARED_MEMORY_BASE_NAME, shared_memory_base_name.c_str());
		}
		
		if(!mysql_real_connect(conn_,phost,puser,ppassword,pdatabase,port,punix_socket,0)) {
			std::string err="unknown";
			try { err = mysql_error(conn_); }catch(...){}
			mysql_close(conn_);
			throw cppdb_myerror(err);
		}

	}
	~connection()
	{
		mysql_close(conn_);
	}
	// API 
	
	void exec(std::string const &s) 
	{
		if(mysql_real_query(conn_,s.c_str(),s.size())) {
			throw cppdb_myerror(mysql_error(conn_));
		}
	}

	///
	/// Start new isolated transaction. Would not be called
	/// withing other transaction on current connection.
	///
	virtual void begin() 
	{
		exec("BEGIN");
	}
	///
	/// Commit the transaction, you may assume that is called after begin()
	/// was called.
	///
	virtual void commit() 
	{
		exec("COMMIT");
	}
	///
	/// Rollback the transaction. MUST never throw!!!
	///
	virtual void rollback() 
	{
		try {
			exec("ROLLBACK");
		}
		catch(...) {
		}
	}
	///
	/// Create a prepared statement \a q. May throw if preparation had failed.
	/// Should never return null value.
	///
	virtual backend::statement *prepare_statement(std::string const &q)
	{
		return new prep::statement(q,conn_);
	}
	virtual backend::statement *create_statement(std::string const &q)
	{
		return new unprep::statement(q,conn_);
	}
	///
	/// Escape a string for inclusion in SQL query. May throw not_supported_by_backend() if not supported by backend.
	///
	virtual std::string escape(std::string const &s) 
	{
		return escape(s.c_str(),s.c_str()+s.size());
	}
	///
	/// Escape a string for inclusion in SQL query. May throw not_supported_by_backend() if not supported by backend.
	///
	virtual std::string escape(char const *s)
	{
		return escape(s,s+strlen(s));
	}
	///
	/// Escape a string for inclusion in SQL query. May throw not_supported_by_backend() if not supported by backend.
	///
	virtual std::string escape(char const *b,char const *e) 
	{
		std::vector<char> buf(2*(e-b)+1);
		size_t len = mysql_real_escape_string(conn_,&buf.front(),b,e-b);
		std::string result;
		result.assign(&buf.front(),len);
		return result;
	}
	///
	/// Get the name of the driver, for example sqlite3, odbc
	///
	virtual std::string driver() 
	{
		return "mysql";
	}
	///
	/// Get the name of the SQL Server, for example sqlite3, mssql, oracle, differs from driver() when
	/// the backend supports multiple databases like odbc backend.
	///
	virtual std::string engine()
	{
		return "mysql";
	}

	// API
	
private:
	///
	/// Set a custom MYSQL option on the connection.
	/// 
	///
	void mysql_set_option(mysql_option option, const void* arg)
	{
		// char can be casted to void but not the other way, support older API
		if(mysql_options(conn_, option, static_cast<char const *>(arg))) {
			throw cppdb_error("cppdb::mysql failed to set option");
		}
	}
	connection_info ci_;
	MYSQL *conn_;
};


} // backend
} // cppdb

extern "C" {
	CPPDB_DRIVER_API cppdb::backend::connection *cppdb_mysql_get_connection(cppdb::connection_info const &cs)
	{
		return new cppdb::mysql_backend::connection(cs);
	}
}
