/*
Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.

The MySQL Connector/C++ is licensed under the terms of the GPLv2
<http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
MySQL Connectors. There are special exceptions to the terms and
conditions of the GPLv2 as it is applied to this software, see the
FLOSS License Exception
<http://www.mysql.com/about/legal/licensing/foss-exception.html>.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
*/


#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include <iostream>
#include <sstream>
#include <boost/variant.hpp>
#include <boost/scoped_array.hpp>

#include <cppconn/exception.h>
#include "mysql_util.h"
#include "mysql_connection.h"
#include "mysql_statement.h"
#include "mysql_prepared_statement.h"
#include "mysql_ps_resultset.h"
#include "mysql_ps_resultset_metadata.h"
#include "mysql_parameter_metadata.h"
#include "mysql_warning.h"
#include "mysql_resultbind.h"

#include "nativeapi/native_statement_wrapper.h"


#include "mysql_debug.h"


namespace sql
{

namespace mysql
{

static const unsigned int MAX_SEND_LONGDATA_BUFFER= 1 << 18; //1<<18=256k (for istream)
static const unsigned int MAX_SEND_LONGDATA_CHUNK=  1 << 18; //1<<19=512k (for string)

// Visitor class to send long data contained in blob_bind
class LongDataSender : public boost::static_visitor<bool>
{
	unsigned	position;
	boost::shared_ptr< NativeAPI::NativeStatementWrapper > proxy;
	boost::shared_ptr<MySQL_DebugLogger> logger;

	LongDataSender()
	{}

public:

	LongDataSender(unsigned int i, boost::shared_ptr< NativeAPI::NativeStatementWrapper > & _proxy,
    boost::shared_ptr<MySQL_DebugLogger> _logger)
		: position	( i			)
		, proxy		( _proxy	)
		, logger	( _logger	)
	{
	}

	bool operator()(std::istream * my_blob) const
	{
		CPP_ENTER("LongDataSender::operator()(std::istream *)");
		if (my_blob == NULL)
			return false;

		//char buf[MAX_SEND_LONGDATA_BUFFER];
		boost::scoped_array<char> buf(new char[MAX_SEND_LONGDATA_BUFFER]);

		do {
			if (my_blob->eof()) {
				break;
			}
			my_blob->read(buf.get(), MAX_SEND_LONGDATA_BUFFER);

			if (my_blob->bad()) {
				throw SQLException("Error while reading from blob (bad)");
			} else if (my_blob->fail()) {
				if (!my_blob->eof()) {
					throw SQLException("Error while reading from blob (fail)");
				}
			}
			if (proxy->send_long_data(position, buf.get(), static_cast<unsigned long>(my_blob->gcount()))) {
				CPP_ERR_FMT("Couldn't send long data : %d:(%s) %s", proxy->errNo(), proxy->sqlstate().c_str(), proxy->error().c_str());
				switch (proxy->errNo()) {
				case CR_OUT_OF_MEMORY:
					throw std::bad_alloc();
				case CR_INVALID_BUFFER_USE:
					throw InvalidArgumentException("MySQL_Prepared_Statement::setBlob: can't set blob value on that column");
				case CR_SERVER_GONE_ERROR:
				case CR_COMMANDS_OUT_OF_SYNC:
				default:
					sql::mysql::util::throwSQLException(*proxy.get());
				}
			}
		} while (1);

		return true;
	}

	bool operator()(sql::SQLString * str) const
	{
		CPP_ENTER("LongDataSender::operator()(sql::SQLString *)");
		if ( str == NULL )
			return false;

		unsigned int sent= 0, chunkSize;

		while (sent < str->length())
		{
			chunkSize= (sent + MAX_SEND_LONGDATA_CHUNK > str->length()
						? str->length() - sent
						: MAX_SEND_LONGDATA_CHUNK);

			if (proxy->send_long_data(position, str->c_str() + sent, chunkSize)) {
				CPP_ERR_FMT("Couldn't send long data : %d:(%s) %s", proxy->errNo(), proxy->sqlstate().c_str(), proxy->error().c_str());
				switch (proxy->errNo()) {
				case CR_OUT_OF_MEMORY:
					throw std::bad_alloc();
				case CR_INVALID_BUFFER_USE:
					throw InvalidArgumentException("MySQL_Prepared_Statement::setBlob: can't set blob value on that column");
				case CR_SERVER_GONE_ERROR:
				case CR_COMMANDS_OUT_OF_SYNC:
				default:
					sql::mysql::util::throwSQLException(*proxy.get());
				}
			}

			sent+= chunkSize;
		}

		return true;
	}
};


class BlobBindDeleter : public boost::static_visitor<>
{
public:

	void operator()(sql::SQLString *& str) const
	{
		if (str != NULL) {
		delete str;
		str= NULL;
		}
	}

	void operator()(std::istream *& my_blob) const
	{
		if (my_blob!= NULL) {
			delete my_blob;
			my_blob= NULL;
		}
	}
};

class BlobIsNull : public boost::static_visitor<bool>
{
public:

	bool operator()(sql::SQLString *& str) const
	{
		return str == NULL;
	}

	bool operator()(std::istream *& my_blob) const
	{
		return my_blob == NULL;
	}
};


void resetBlobBind(MYSQL_BIND & param)
{
	delete [] static_cast<char *>(param.buffer);

	param.buffer_type=		MYSQL_TYPE_LONG_BLOB;
	param.buffer=			NULL;
	param.buffer_length=	0;
	param.is_null_value=	0;

	delete param.length;
	param.length=			new unsigned long(0);
}


class MySQL_ParamBind
{
public:

	typedef boost::variant< std::istream *, sql::SQLString *> Blob_t;

private:

	unsigned int param_count;
	boost::scoped_array< MYSQL_BIND > bind;
	boost::scoped_array< bool > value_set;
	boost::scoped_array< bool > delete_blob_after_execute;

	typedef std::map<unsigned int, Blob_t > Blobs;

	Blobs blob_bind;

public:

	MySQL_ParamBind(unsigned int paramCount)
		: param_count(paramCount), bind(NULL), value_set(NULL),
		  delete_blob_after_execute(NULL)
	{
		if (param_count) {
			bind.reset(new MYSQL_BIND[paramCount]);
			memset(bind.get(), 0, sizeof(MYSQL_BIND) * paramCount);

			value_set.reset(new bool[paramCount]);
			delete_blob_after_execute.reset(new bool[paramCount]);
			for (unsigned int i = 0; i < paramCount; ++i) {
				bind[i].is_null_value = 1;
				value_set[i] = false;
				delete_blob_after_execute[i] = false;
			}
		}
	}

	virtual ~MySQL_ParamBind()
	{
		clearParameters();

		for (Blobs::iterator it= blob_bind.begin();
			it != blob_bind.end(); ++it) {
			if (delete_blob_after_execute[it->first]) {
				delete_blob_after_execute[it->first] = false;
				boost::apply_visitor(::sql::mysql::BlobBindDeleter(), it->second);
			}
		}
	}

	void set(unsigned int position)
	{
		value_set[position] = true;
	}

	void unset(unsigned int position)
	{
		value_set[position] = false;
		if (delete_blob_after_execute[position]) {
			delete_blob_after_execute[position] = false;
			boost::apply_visitor(::sql::mysql::BlobBindDeleter(),blob_bind[position]);
			blob_bind.erase(position);
		}
	}


	void setBlob(unsigned int position, Blob_t & blob, bool delete_after_execute)
	{
		set(position);

		resetBlobBind(bind[position]);

		Blobs::iterator it = blob_bind.find(position);
		if (it != blob_bind.end() && delete_blob_after_execute[position]) {
				boost::apply_visitor(::sql::mysql::BlobBindDeleter(), it->second);
			}

		if (boost::apply_visitor(::sql::mysql::BlobIsNull(), blob))
		{
		  if (it != blob_bind.end())
			blob_bind.erase(it);

		  delete_blob_after_execute[position] = false;
		}
		else
		{
			  blob_bind[position] = blob;
			  delete_blob_after_execute[position] = delete_after_execute;
		}
	}


	bool isAllSet()
	{
		for (unsigned int i = 0; i < param_count; ++i) {
			if (!value_set[i]) {
				return false;
			}
		}
		return true;
	}


	void clearParameters()
	{
		for (unsigned int i = 0; i < param_count; ++i) {
			delete (char*) bind[i].length;
			bind[i].length = NULL;
			delete[] (char*) bind[i].buffer;
			bind[i].buffer = NULL;
			if (value_set[i]) {
				Blobs::iterator it= blob_bind.find(i);
				if (it != blob_bind.end() && delete_blob_after_execute[i]) {
					boost::apply_visitor(::sql::mysql::BlobBindDeleter(), it->second);
					blob_bind.erase(it);
				}
				blob_bind[i] = Blob_t();
				value_set[i] = false;
			}
		}
	}


	// Name get() was too confusing, since class objects are used with smart pointers
	MYSQL_BIND * getBindObject()
	{
		return bind.get();
	}


	boost::variant< std::istream *, SQLString *> getBlobObject(unsigned int position)
	{
		Blobs::iterator it= blob_bind.find( position );

		if (it != blob_bind.end())
			return it->second;

		return Blob_t();
	}

};


/* {{{ MySQL_Prepared_Statement::MySQL_Prepared_Statement() -I- */
MySQL_Prepared_Statement::MySQL_Prepared_Statement(
			boost::shared_ptr< NativeAPI::NativeStatementWrapper > & s, sql::Connection * conn,
			sql::ResultSet::enum_type rset_type, boost::shared_ptr< MySQL_DebugLogger > & log
		)
	:connection(conn), proxy(s), isClosed(false), warningsHasBeenLoaded(true), logger(log),
		resultset_type(rset_type), result_bind(new MySQL_ResultBind(proxy, logger))

{
	CPP_ENTER("MySQL_Prepared_Statement::MySQL_Prepared_Statement");
	CPP_INFO_FMT("this=%p", this);
	param_count = proxy->param_count();
	param_bind.reset(new MySQL_ParamBind(param_count));

	res_meta.reset(new MySQL_PreparedResultSetMetaData(proxy, logger));
	param_meta.reset(new MySQL_ParameterMetaData(proxy));
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::~MySQL_Prepared_Statement() -I- */
MySQL_Prepared_Statement::~MySQL_Prepared_Statement()
{
	CPP_ENTER("MySQL_Prepared_Statement::~MySQL_Prepared_Statement");
	/*
	  This will free param_bind.
	  We should not do it or there will be double free.
	*/
	if (!isClosed) {
		closeIntern();
	}
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::sendLongDataBeforeParamBind() -I- */
bool
MySQL_Prepared_Statement::sendLongDataBeforeParamBind()
{
	CPP_ENTER("MySQL_Prepared_Statement::sendLongDataBeforeParamBind");

	MYSQL_BIND * bind= param_bind->getBindObject();

	for (unsigned int i = 0; i < param_count; ++i) {
		if (bind[i].buffer_type == MYSQL_TYPE_LONG_BLOB) {
			::sql::mysql::LongDataSender lv(i, proxy, logger);
      MySQL_ParamBind::Blob_t dummy(param_bind->getBlobObject(i));
			boost::apply_visitor(lv, dummy);
		}

	}
	return true;
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::do_query() -I- */
void
MySQL_Prepared_Statement::do_query()
{
	CPP_ENTER("MySQL_Prepared_Statement::do_query");
	if (param_count && !param_bind->isAllSet()) {
		CPP_ERR("Value not set for all parameters");
		throw sql::SQLException("Value not set for all parameters");
	}
	if (proxy->bind_param(param_bind->getBindObject())) {
		CPP_ERR_FMT("Couldn't bind : %d:(%s) %s", proxy->errNo(), proxy->sqlstate().c_str(), proxy->error().c_str());
		sql::mysql::util::throwSQLException(*proxy.get());
	}
	if (!sendLongDataBeforeParamBind() || proxy->execute()) {
		CPP_ERR_FMT("Couldn't execute : %d:(%s) %s", proxy->errNo(), proxy->sqlstate().c_str(), proxy->error().c_str());
		sql::mysql::util::throwSQLException(*proxy.get());
	}

	warningsHasBeenLoaded= false;
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::clearParameters() -I- */
void
MySQL_Prepared_Statement::clearParameters()
{
	CPP_ENTER("MySQL_Prepared_Statement::clearParameters");
	CPP_INFO_FMT("this=%p", this);
	checkClosed();
	param_bind->clearParameters();
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::getConnection() -I- */
sql::Connection *
MySQL_Prepared_Statement::getConnection()
{
	CPP_ENTER("MySQL_Prepared_Statement::getConnection");
	CPP_INFO_FMT("this=%p", this);
	checkClosed();
	return connection;
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::execute() -I- */
bool
MySQL_Prepared_Statement::execute()
{
	CPP_ENTER("MySQL_Prepared_Statement::execute");
	CPP_INFO_FMT("this=%p", this);
	checkClosed();
	do_query();
	return (proxy->field_count() > 0);
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::execute() -U- */
bool
MySQL_Prepared_Statement::execute(const sql::SQLString&)
{
	CPP_ENTER("MySQL_Prepared_Statement::execute(const sql::SQLString& sql)");
	throw sql::MethodNotImplementedException("MySQL_Prepared_Statement::execute");
	return false; // fool compilers
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::executeQuery() -I- */
sql::ResultSet *
MySQL_Prepared_Statement::executeQuery()
{
	CPP_ENTER("MySQL_Prepared_Statement::executeQuery");
	CPP_INFO_FMT("this=%p", this);
	checkClosed();

	do_query();

	my_bool	bool_tmp=1;
	proxy->attr_set( STMT_ATTR_UPDATE_MAX_LENGTH, &bool_tmp);
	sql::ResultSet::enum_type tmp_type;
	if (resultset_type == sql::ResultSet::TYPE_SCROLL_INSENSITIVE) {
		if (proxy->store_result()) {
			sql::mysql::util::throwSQLException(*proxy.get());
		}
		tmp_type = sql::ResultSet::TYPE_SCROLL_INSENSITIVE;
	} else if (resultset_type == sql::ResultSet::TYPE_FORWARD_ONLY) {
		tmp_type = sql::ResultSet::TYPE_FORWARD_ONLY;
	} else {
		throw SQLException("Invalid value for result set type");
	}
	sql::ResultSet * tmp = new MySQL_Prepared_ResultSet(proxy, result_bind, tmp_type, this, logger);

	CPP_INFO_FMT("rset=%p", tmp);
	return tmp;
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::executeQuery() -U- */
sql::ResultSet *
MySQL_Prepared_Statement::executeQuery(const sql::SQLString&)
{
	throw sql::MethodNotImplementedException("MySQL_Prepared_Statement::executeQuery"); /* TODO - what to do? Comes from Statement */
	return NULL; // fool compilers
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::executeUpdate() -I- */
int
MySQL_Prepared_Statement::executeUpdate()
{
	CPP_ENTER("MySQL_Prepared_Statement::executeUpdate");
	CPP_INFO_FMT("this=%p", this);
	checkClosed();
	do_query();
	return static_cast<int>(proxy->affected_rows());
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::executeUpdate() -U- */
int
MySQL_Prepared_Statement::executeUpdate(const sql::SQLString&)
{
	throw sql::MethodNotImplementedException("MySQL_Prepared_Statement::executeUpdate"); /* TODO - what to do? Comes from Statement */
	return 0; // fool compilers
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setBigInt() -I- */
void
MySQL_Prepared_Statement::setBigInt(unsigned int parameterIndex, const sql::SQLString& value)
{
	CPP_ENTER("MySQL_Prepared_Statement::setBigInt");
	setString(parameterIndex, value);
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setBlob_intern() -I- */
/*
void
setBlob_intern(unsigned int parameterIndex
										 , / *boost::variant< std::istream *, sql::SQLString *>* /MySQL_ParamBind::Blob_t & blob
                                         , bool deleteBlobAfterExecute)
{
	CPP_ENTER("MySQL_Prepared_Statement::setBlob_intern");
	CPP_INFO_FMT("this=%p", this);
	checkClosed();

	--parameterIndex; / * DBC counts from 1 * /

	param_bind->set(parameterIndex);
	MYSQL_BIND * param = &param_bind->getBindObject()[parameterIndex];

	delete [] static_cast<char *>(param->buffer);

	param->buffer_type	= MYSQL_TYPE_LONG_BLOB;
	param->buffer		= NULL;
	param->buffer_length= 0;
	param->is_null_value= 0;

	delete param->length;
	param->length = new unsigned long(0);

	param_bind->setBlob(parameterIndex, blob, deleteBlobAfterExecute);
}*/

/* }}} */


/* {{{ MySQL_Prepared_Statement::setBlob() -I- */
void
MySQL_Prepared_Statement::setBlob(unsigned int parameterIndex, std::istream * blob)
{
	CPP_ENTER("MySQL_Prepared_Statement::setBlob");
	CPP_INFO_FMT("this=%p", this);
	checkClosed();

	if (parameterIndex == 0 || parameterIndex > param_count) {
		throw InvalidArgumentException("MySQL_Prepared_Statement::setBlob: invalid 'parameterIndex'");
	}

	MySQL_ParamBind::Blob_t dummy(blob);
	param_bind->setBlob(--parameterIndex, dummy, false);
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setBoolean() -I- */
void
MySQL_Prepared_Statement::setBoolean(unsigned int parameterIndex, bool value)
{
	CPP_ENTER("MySQL_Prepared_Statement::setBoolean");
	setInt(parameterIndex, value);
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setDateTime() -I- */
void
MySQL_Prepared_Statement::setDateTime(unsigned int parameterIndex, const sql::SQLString& value)
{
	CPP_ENTER("MySQL_Prepared_Statement::setDateTime");
	setString(parameterIndex, value);
}
/* }}} */

typedef std::pair<char *, size_t> BufferSizePair;
static BufferSizePair
allocate_buffer_for_type(enum_field_types t)
{
	switch (t) {
#if A1
		// We don't use these now. When we have setXXX, we can enable them
		case MYSQL_TYPE_TINY:
			return BufferSizePair(new char[1], 1);
		case MYSQL_TYPE_SHORT:
			return BufferSizePair(new char[2], 2);
		case MYSQL_TYPE_INT24:
		case MYSQL_TYPE_FLOAT:
#endif
		case MYSQL_TYPE_LONG:
			return BufferSizePair(new char[4], 4);
		case MYSQL_TYPE_DOUBLE:
		case MYSQL_TYPE_LONGLONG:
			return BufferSizePair(new char[8], 8);
#if A1
		// We don't use these now. When we have setXXX, we can enable them
		case MYSQL_TYPE_NEWDATE:
		case MYSQL_TYPE_DATE:
		case MYSQL_TYPE_TIME:
		case MYSQL_TYPE_DATETIME:
			return BufferSizePair(new char[sizeof(MYSQL_TIME)], sizeof(MYSQL_TIME));
		case MYSQL_TYPE_BLOB:
		case MYSQL_TYPE_VAR_STRING:
#endif
		case MYSQL_TYPE_STRING:
			return BufferSizePair(NULLCSTR, 0);

#if A1
		// We don't use these now. When we have setXXX, we can enable them
		case MYSQL_TYPE_DECIMAL:
		case MYSQL_TYPE_NEWDECIMAL:
			return BufferSizePair(new char[64], 64);
		case MYSQL_TYPE_TIMESTAMP:
		case MYSQL_TYPE_YEAR:
			return BufferSizePair(new char[10], 10);
#endif
#if A0
		// There two are not sent over the wire
		case MYSQL_TYPE_SET:
		case MYSQL_TYPE_ENUM:
#endif
#if A1
		// We don't use these now. When we have setXXX, we can enable them
		case MYSQL_TYPE_GEOMETRY:
		case MYSQL_TYPE_BIT:
#endif
		case MYSQL_TYPE_NULL:
			return BufferSizePair(NULLCSTR, 0);
		default:
			throw sql::InvalidArgumentException("allocate_buffer_for_type: invalid result_bind data type");
	}
}


/* {{{ MySQL_Prepared_Statement::setDouble() -I- */
void
MySQL_Prepared_Statement::setDouble(unsigned int parameterIndex, double value)
{
	CPP_ENTER("MySQL_Prepared_Statement::setDouble");
	CPP_INFO_FMT("this=%p %f", this, value);
	checkClosed();

	if (parameterIndex == 0 || parameterIndex > param_count) {
		throw InvalidArgumentException("MySQL_Prepared_Statement::setDouble: invalid 'parameterIndex'");
	}
	--parameterIndex; /* DBC counts from 1 */

	{
		MySQL_ParamBind::Blob_t dummy;
    param_bind->setBlob(parameterIndex, dummy, false);
		param_bind->unset(parameterIndex);
	}

	enum_field_types t = MYSQL_TYPE_DOUBLE;

	BufferSizePair p = allocate_buffer_for_type(t);

	param_bind->set(parameterIndex);
	MYSQL_BIND * param = &param_bind->getBindObject()[parameterIndex];

	param->buffer_type	= t;
	delete [] static_cast<char *>(param->buffer);
	param->buffer = p.first;
	param->buffer_length = 0;
	param->is_null_value = 0;
	delete param->length;
	param->length	= NULL;

	memcpy(param->buffer, &value, p.second);
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setInt() -I- */
void
MySQL_Prepared_Statement::setInt(unsigned int parameterIndex, int32_t value)
{
	CPP_ENTER("MySQL_Prepared_Statement::setInt");
	CPP_INFO_FMT("this=%p", this);
	CPP_INFO_FMT("column=%u value=%d", parameterIndex, value);
	checkClosed();

	if (parameterIndex == 0 || parameterIndex > param_count) {
		throw InvalidArgumentException("MySQL_Prepared_Statement::setInt: invalid 'parameterIndex'");
	}
	--parameterIndex; /* DBC counts from 1 */

	{
		MySQL_ParamBind::Blob_t dummy;
		param_bind->setBlob(parameterIndex, dummy, false);
		param_bind->unset(parameterIndex);
	}

	enum_field_types t = MYSQL_TYPE_LONG;

	BufferSizePair p = allocate_buffer_for_type(t);

	param_bind->set(parameterIndex);
	MYSQL_BIND * param = &param_bind->getBindObject()[parameterIndex];

	param->buffer_type	= t;
	delete [] static_cast<char *>(param->buffer);
	param->buffer		= p.first;
	param->buffer_length = 0;
	param->is_null_value = 0;
	delete param->length;
	param->length		= NULL;

	memcpy(param->buffer, &value, p.second);
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setUInt() -I- */
void
MySQL_Prepared_Statement::setUInt(unsigned int parameterIndex, uint32_t value)
{
	CPP_ENTER("MySQL_Prepared_Statement::setUInt");
	CPP_INFO_FMT("this=%p", this);
	CPP_INFO_FMT("column=%u value=%u", parameterIndex, value);
	checkClosed();

	if (parameterIndex == 0 || parameterIndex > param_count) {
		throw InvalidArgumentException("MySQL_Prepared_Statement::setInt: invalid 'parameterIndex'");
	}
	--parameterIndex; /* DBC counts from 1 */

	{
		MySQL_ParamBind::Blob_t dummy;
    param_bind->setBlob(parameterIndex, dummy, false);
		param_bind->unset(parameterIndex);
	}

	enum_field_types t = MYSQL_TYPE_LONG;

	BufferSizePair p = allocate_buffer_for_type(t);

	param_bind->set(parameterIndex);
	MYSQL_BIND * param = &param_bind->getBindObject()[parameterIndex];

	param->buffer_type	= t;
	delete [] static_cast<char *>(param->buffer);
	param->buffer		= p.first;
	param->buffer_length = 0;
	param->is_null_value = 0;
	param->is_unsigned	= 1;
	delete param->length;
	param->length		= NULL;

	memcpy(param->buffer, &value, p.second);
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setInt64() -I- */
void
MySQL_Prepared_Statement::setInt64(unsigned int parameterIndex, int64_t value)
{
	CPP_ENTER("MySQL_Prepared_Statement::setInt64");
	CPP_INFO_FMT("this=%p", this);
	checkClosed();

	if (parameterIndex == 0 || parameterIndex > param_count) {
		throw InvalidArgumentException("MySQL_Prepared_Statement::setInt64: invalid 'parameterIndex'");
	}
	--parameterIndex; /* DBC counts from 1 */

	{
		MySQL_ParamBind::Blob_t dummy;
		param_bind->setBlob(parameterIndex, dummy, false);
		param_bind->unset(parameterIndex);
	}

	enum_field_types t = MYSQL_TYPE_LONGLONG;

	BufferSizePair p = allocate_buffer_for_type(t);

	param_bind->set(parameterIndex);
	MYSQL_BIND * param = &param_bind->getBindObject()[parameterIndex];

	param->buffer_type	= t;
	delete [] static_cast<char *>(param->buffer);
	param->buffer		= p.first;
	param->buffer_length = 0;
	param->is_null_value = 0;
	delete param->length;
	param->length = NULL;

	memcpy(param->buffer, &value, p.second);
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setUInt64() -I- */
void
MySQL_Prepared_Statement::setUInt64(unsigned int parameterIndex, uint64_t value)
{
	CPP_ENTER("MySQL_Prepared_Statement::setUInt64");
	CPP_INFO_FMT("this=%p", this);
	checkClosed();

	if (parameterIndex == 0 || parameterIndex > param_count) {
		throw InvalidArgumentException("MySQL_Prepared_Statement::setUInt64: invalid 'parameterIndex'");
	}
	--parameterIndex; /* DBC counts from 1 */

	{
		MySQL_ParamBind::Blob_t dummy;
		param_bind->setBlob(parameterIndex, dummy, false);
		param_bind->unset(parameterIndex);
	}


	enum_field_types t = MYSQL_TYPE_LONGLONG;

	BufferSizePair p = allocate_buffer_for_type(t);

	param_bind->set(parameterIndex);
	MYSQL_BIND * param = &param_bind->getBindObject()[parameterIndex];

	param->buffer_type	= t;
	delete [] static_cast<char *>(param->buffer);
	param->buffer		= p.first;
	param->buffer_length = 0;
	param->is_null_value = 0;
	param->is_unsigned = 1;
	delete param->length;
	param->length = NULL;

	memcpy(param->buffer, &value, p.second);
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setNull() -I- */
void
MySQL_Prepared_Statement::setNull(unsigned int parameterIndex, int /* sqlType */)
{
	CPP_ENTER("MySQL_Prepared_Statement::setNull");
	CPP_INFO_FMT("this=%p", this);
	CPP_INFO_FMT("column=%u", parameterIndex);
	checkClosed();

	if (parameterIndex == 0 || parameterIndex > param_count) {
		throw InvalidArgumentException("MySQL_Prepared_Statement::setNull: invalid 'parameterIndex'");
	}
	--parameterIndex; /* DBC counts from 1 */

	{
		MySQL_ParamBind::Blob_t dummy;
		param_bind->setBlob(parameterIndex, dummy, false);
		param_bind->unset(parameterIndex);
	}

	enum_field_types t = MYSQL_TYPE_NULL;

	BufferSizePair p = allocate_buffer_for_type(t);

	param_bind->set(parameterIndex);
	MYSQL_BIND * param = &param_bind->getBindObject()[parameterIndex];

	param->buffer_type	= t;
	delete [] static_cast<char *>(param->buffer);
	param->buffer = NULL;
	delete param->length;
	param->length = NULL;
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setString() -I- */
void
MySQL_Prepared_Statement::setString(unsigned int parameterIndex, const sql::SQLString& value)
{
	CPP_ENTER("MySQL_Prepared_Statement::setString");
	CPP_INFO_FMT("this=%p", this);
	CPP_INFO_FMT("column=%u value_len=%d value=%s ", parameterIndex, value.length(), value.c_str());
	checkClosed();

	if (parameterIndex == 0 || parameterIndex > param_count) {
		CPP_ERR("Invalid parameterIndex");
		throw InvalidArgumentException("MySQL_Prepared_Statement::setString: invalid 'parameterIndex'");
	}
	if (value.length() > 256*1024) {
		MySQL_ParamBind::Blob_t dummy(const_cast<sql::SQLString*>(&value));
		return param_bind->setBlob(--parameterIndex, dummy, false);
	}

	--parameterIndex; /* DBC counts from 1 */

	{
		MySQL_ParamBind::Blob_t dummy;
		param_bind->setBlob(parameterIndex, dummy, false);
		param_bind->unset(parameterIndex);
	}

	enum_field_types t = MYSQL_TYPE_STRING;

	param_bind->set(parameterIndex);
	MYSQL_BIND * param = &param_bind->getBindObject()[parameterIndex];

	delete [] static_cast<char *>(param->buffer);

	param->buffer_type	= t;
	param->buffer		= memcpy(new char[value.length() + 1], value.c_str(), value.length() + 1);
	param->buffer_length= static_cast<unsigned long>(value.length()) + 1;
	param->is_null_value= 0;

	delete param->length;
	param->length = new unsigned long(static_cast<unsigned long>(value.length()));
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::cancel() -U- */
void
MySQL_Prepared_Statement::cancel()
{
	CPP_ENTER("MySQL_Prepared_Statement::cancel");
	CPP_INFO_FMT("this=%p", this);
	checkClosed();
	throw MethodNotImplementedException("MySQL_Prepared_Statement::cancel");
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::getFetchSize() -U- */
size_t
MySQL_Prepared_Statement::getFetchSize()
{
	checkClosed();
	throw MethodNotImplementedException("MySQL_Prepared_Statement::getFetchSize");
	return 0; // fool compilers
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::getMetaData() -I- */
sql::ResultSetMetaData *
MySQL_Prepared_Statement::getMetaData()
{
	CPP_ENTER("MySQL_Prepared_Statement::getMetaData");
	CPP_INFO_FMT("this=%p", this);
	checkClosed();
	return res_meta.get();
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::getParameterMetaData() -I- */
sql::ParameterMetaData *
MySQL_Prepared_Statement::getParameterMetaData()
{
	CPP_ENTER("MySQL_Prepared_Statement::getParameterMetaData");
	CPP_INFO_FMT("this=%p", this);
	checkClosed();
	return param_meta.get();
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::getResultSet() -I- */
sql::ResultSet *
MySQL_Prepared_Statement::getResultSet()
{
	CPP_ENTER("MySQL_Prepared_Statement::getResultSet");
	CPP_INFO_FMT("this=%p", this);
	checkClosed();
	if (proxy->more_results() && proxy->next_result()) {
		sql::mysql::util::throwSQLException(*proxy.get());
	}

	my_bool	bool_tmp = 1;
	proxy->attr_set(STMT_ATTR_UPDATE_MAX_LENGTH, &bool_tmp);
	sql::ResultSet::enum_type tmp_type;
	if (resultset_type == sql::ResultSet::TYPE_SCROLL_INSENSITIVE) {
		if (proxy->store_result()) {
			sql::mysql::util::throwSQLException(*proxy.get());
		}
		tmp_type = sql::ResultSet::TYPE_SCROLL_INSENSITIVE;
	} else if (resultset_type == sql::ResultSet::TYPE_FORWARD_ONLY) {
		tmp_type = sql::ResultSet::TYPE_FORWARD_ONLY;
	} else {
		throw SQLException("Invalid value for result set type");
	}

	sql::ResultSet * tmp = new MySQL_Prepared_ResultSet(proxy, result_bind, tmp_type, this, logger);

	CPP_INFO_FMT("rset=%p", tmp);
	return tmp;
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setFetchSize() -U- */
void
MySQL_Prepared_Statement::setFetchSize(size_t /* size */)
{
	checkClosed();
	throw MethodNotImplementedException("MySQL_Prepared_Statement::setFetchSize");
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setQueryTimeout() -U- */
void
MySQL_Prepared_Statement::setQueryTimeout(unsigned int)
{
	checkClosed();
	throw MethodNotImplementedException("MySQL_Prepared_Statement::setQueryTimeout");
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::clearWarnings() -I- */
void
MySQL_Prepared_Statement::clearWarnings()
{
	CPP_ENTER("MySQL_Prepared_Statement::clearWarnings");
	CPP_INFO_FMT("this=%p", this);
	checkClosed();
	if (warnings)
	{
		warnings.reset();
	}
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::close() -I- */
void
MySQL_Prepared_Statement::close()
{
	CPP_ENTER("MySQL_Prepared_Statement::close");
	CPP_INFO_FMT("this=%p", this);
	checkClosed();
	closeIntern();
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::getMaxFieldSize() -U- */
unsigned int
MySQL_Prepared_Statement::getMaxFieldSize()
{
	checkClosed();
	throw MethodNotImplementedException("MySQL_Prepared_Statement::getMaxFieldSize");
	return 0; // fool compilers
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::getMaxRows() -U- */
uint64_t
MySQL_Prepared_Statement::getMaxRows()
{
	checkClosed();
	throw MethodNotImplementedException("MySQL_Prepared_Statement::getMaxRows");
	return 0; // fool compilers
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::getMoreResults() -U- */
bool
MySQL_Prepared_Statement::getMoreResults()
{
	checkClosed();
	throw MethodNotImplementedException("MySQL_Prepared_Statement::getMoreResults");
	return false; // fool compilers
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::getQueryTimeout() -U- */
unsigned int
MySQL_Prepared_Statement::getQueryTimeout()
{
	checkClosed();
	throw MethodNotImplementedException("MySQL_Prepared_Statement::getQueryTimeout");
	return 0; // fool compilers
}
/* }}} */


/* {{{ MySQL_Statement::getResultSetType() -I- */
sql::ResultSet::enum_type
MySQL_Prepared_Statement::getResultSetType()
{
	CPP_ENTER("MySQL_Statement::getResultSetType");
	checkClosed();
	return resultset_type;
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::getUpdateCount() -U- */
uint64_t
MySQL_Prepared_Statement::getUpdateCount()
{
	checkClosed();
	throw MethodNotImplementedException("MySQL_Prepared_Statement::getUpdateCount");
	return 0; // fool compilers
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::getWarnings() -I- */
const SQLWarning *
MySQL_Prepared_Statement::getWarnings()
{
	CPP_ENTER("MySQL_Prepared_Statement::getWarnings");
	CPP_INFO_FMT("this=%p", this);
	checkClosed();

	if (!warningsHasBeenLoaded)
	{
		warnings.reset( loadMysqlWarnings(connection) );
		warningsHasBeenLoaded= true;
	}

	return warnings.get();
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setCursorName() -U- */
void
MySQL_Prepared_Statement::setCursorName(const sql::SQLString &)
{
	checkClosed();
	throw MethodNotImplementedException("MySQL_Prepared_Statement::setCursorName");
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setEscapeProcessing() -U- */
void
MySQL_Prepared_Statement::setEscapeProcessing(bool)
{
	checkClosed();
	throw MethodNotImplementedException("MySQL_Prepared_Statement::setEscapeProcessing");
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setMaxFieldSize() -U- */
void
MySQL_Prepared_Statement::setMaxFieldSize(unsigned int)
{
	checkClosed();
	throw MethodNotImplementedException("MySQL_Prepared_Statement::setMaxFieldSize");
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setMaxRows() -U- */
void
MySQL_Prepared_Statement::setMaxRows(unsigned int)
{
	checkClosed();
	throw MethodNotImplementedException("MySQL_Prepared_Statement::setMaxRows");
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setResultSetConcurrency() -U- */
void
MySQL_Prepared_Statement::setResultSetConcurrency(int)
{
	checkClosed();
	throw MethodNotImplementedException("MySQL_Prepared_Statement::setResultSetConcurrency");
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::setResultSetType() -U- */
sql::PreparedStatement *
MySQL_Prepared_Statement::setResultSetType(sql::ResultSet::enum_type /* type */)
{
	checkClosed();
#if WE_SUPPORT_USE_RESULT_WITH_PS
		/* The connector is not ready for unbuffered as we need to refetch */
	resultset_type = type;
#else
	throw MethodNotImplementedException("MySQL_Prepared_Statement::setResultSetType");
#endif
	return this;
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::checkClosed() -I- */
void
MySQL_Prepared_Statement::checkClosed()
{
	CPP_ENTER("MySQL_Prepared_Statement::checkClosed");
	if (isClosed) {
		CPP_ERR("Statement has already been closed");
		throw sql::InvalidInstanceException("Statement has been closed");
	}
}
/* }}} */


/* {{{ MySQL_Prepared_Statement::closeIntern() -I- */
void
MySQL_Prepared_Statement::closeIntern()
{
	CPP_ENTER("MySQL_Prepared_Statement::closeIntern");
	proxy.reset();
	clearParameters();
	clearWarnings();

	isClosed = true;
}
/* }}} */

} /* namespace mysql */
} /* namespace sql */

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */
