/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <log4cxx/db/odbcappender.h>
#include <log4cxx/helpers/loglog.h>
#include <log4cxx/helpers/optionconverter.h>
#include <log4cxx/helpers/stringhelper.h>
#include <log4cxx/helpers/transcoder.h>
#include <log4cxx/patternlayout.h>
#include <log4cxx/pattern/mdcpatternconverter.h>
#include <apr_strings.h>
#include <apr_time.h>
#include <cmath> // std::pow

#include <log4cxx/pattern/loggerpatternconverter.h>
#include <log4cxx/pattern/classnamepatternconverter.h>
#include <log4cxx/pattern/datepatternconverter.h>
#include <log4cxx/pattern/filelocationpatternconverter.h>
#include <log4cxx/pattern/fulllocationpatternconverter.h>
#include <log4cxx/pattern/shortfilelocationpatternconverter.h>
#include <log4cxx/pattern/linelocationpatternconverter.h>
#include <log4cxx/pattern/messagepatternconverter.h>
#include <log4cxx/pattern/methodlocationpatternconverter.h>
#include <log4cxx/pattern/levelpatternconverter.h>
#include <log4cxx/pattern/threadpatternconverter.h>
#include <log4cxx/pattern/threadusernamepatternconverter.h>
#include <log4cxx/pattern/ndcpatternconverter.h>

#if !defined(LOG4CXX)
	#define LOG4CXX 1
#endif
#include <log4cxx/private/log4cxx_private.h>
#if LOG4CXX_HAVE_ODBC && !LOG4CXX_LOGCHAR_IS_UNICHAR
	#if defined(WIN32) || defined(_WIN32)
		#include <windows.h>
	#endif
	#include <sqlext.h>
#else
	typedef void* SQLHSTMT;
#endif
#include <log4cxx/private/odbcappender_priv.h>
#if defined(min)
	#undef min
#endif
#include <cstring>
#include <algorithm>
#include <limits>

using namespace LOG4CXX_NS;
using namespace LOG4CXX_NS::helpers;
using namespace LOG4CXX_NS::db;
using namespace LOG4CXX_NS::spi;
using namespace LOG4CXX_NS::pattern;

SQLException::SQLException(short fHandleType,
	void* hInput, const char* prolog,
	LOG4CXX_NS::helpers::Pool& p)
	: Exception(formatMessage(fHandleType, hInput, prolog, p))
{
}


SQLException::SQLException(const char* msg)
	: Exception(msg)
{
}

SQLException::SQLException(const SQLException& src)
	: Exception(src)
{
}

const char* SQLException::formatMessage(short fHandleType,
	void* hInput, const char* prolog, LOG4CXX_NS::helpers::Pool& p)
{
	std::string strReturn(prolog);
	strReturn.append(" - ");
#if LOG4CXX_HAVE_ODBC && !LOG4CXX_LOGCHAR_IS_UNICHAR
	SQLCHAR       SqlState[6];
	SQLCHAR       Msg[SQL_MAX_MESSAGE_LENGTH];
	SQLINTEGER    NativeError;
	SQLSMALLINT   i;
	SQLSMALLINT   MsgLen;
	SQLRETURN     rc2;

	// Get the status records.
	i = 1;

	while ((rc2 = SQLGetDiagRecA(fHandleType, hInput, i, SqlState, &NativeError,
					Msg, sizeof(Msg), &MsgLen)) != SQL_NO_DATA)
	{
		strReturn.append((char*) Msg);
		i++;
	}

#else
	strReturn.append("log4cxx built without ODBC support");
#endif

	return apr_pstrdup((apr_pool_t*) p.getAPRPool(), strReturn.c_str());
}


IMPLEMENT_LOG4CXX_OBJECT(ODBCAppender)

#define _priv static_cast<ODBCAppenderPriv*>(m_priv.get())

ODBCAppender::ODBCAppender()
	: AppenderSkeleton (std::make_unique<ODBCAppenderPriv>(
#if LOG4CXX_EVENTS_AT_EXIT
		[this] {
			std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
			if(_priv->buffer.empty() || _priv->closed)
				;
			else try
			{
				flushBuffer(_priv->pool);
			}
			catch (SQLException& e)
			{
				_priv->errorHandler->error(LOG4CXX_STR("Error flushing connection"),
					e, ErrorCode::GENERIC_FAILURE);
			}
		}
#endif
								))
{
}

ODBCAppender::~ODBCAppender()
{
	if (_priv->setClosed())
		_priv->close();
}

#define RULES_PUT(spec, cls) \
	specs.insert(PatternMap::value_type(LogString(LOG4CXX_STR(spec)), cls ::newInstance))

static PatternMap getFormatSpecifiers()
{
	PatternMap specs;
	if (specs.empty())
	{
		RULES_PUT("logger", LoggerPatternConverter);
		RULES_PUT("class", ClassNamePatternConverter);
		RULES_PUT("time", DatePatternConverter);
		RULES_PUT("shortfilename", ShortFileLocationPatternConverter);
		RULES_PUT("fullfilename", FileLocationPatternConverter);
		RULES_PUT("location", FullLocationPatternConverter);
		RULES_PUT("line", LineLocationPatternConverter);
		RULES_PUT("message", MessagePatternConverter);
		RULES_PUT("method", MethodLocationPatternConverter);
		RULES_PUT("level", LevelPatternConverter);
		RULES_PUT("thread", ThreadPatternConverter);
		RULES_PUT("threadname", ThreadUsernamePatternConverter);
		RULES_PUT("mdc", MDCPatternConverter);
		RULES_PUT("ndc", NDCPatternConverter);
	}
	return specs;
}

void ODBCAppender::setOption(const LogString& option, const LogString& value)
{
	if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BUFFERSIZE"), LOG4CXX_STR("buffersize")))
	{
		int parsed = OptionConverter::toInt(value, 1);
		if (parsed < 0)
		{
			parsed = 1;
		}
		setBufferSize((size_t) parsed);
	}
	else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("PASSWORD"), LOG4CXX_STR("password")))
	{
		setPassword(value);
	}
	else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SQL"), LOG4CXX_STR("sql")))
	{
		setSql(value);
	}
	else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("URL"), LOG4CXX_STR("url"))
		|| StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("DSN"), LOG4CXX_STR("dsn"))
		|| StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("CONNECTIONSTRING"), LOG4CXX_STR("connectionstring"))  )
	{
		setURL(value);
	}
	else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("USER"), LOG4CXX_STR("user")))
	{
		setUser(value);
	}
	else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("COLUMNMAPPING"), LOG4CXX_STR("columnmapping")))
	{
		_priv->mappedName.push_back(value);
	}
	else
	{
		AppenderSkeleton::setOption(option, value);
	}
}

// Does ODBCAppender require a layout?

bool ODBCAppender::requiresLayout() const
{
	return false;
}

void ODBCAppender::activateOptions( LOG4CXX_ACTIVATE_OPTIONS_FORMAL_PARAMETERS )
{
#if !LOG4CXX_HAVE_ODBC || LOG4CXX_LOGCHAR_IS_UNICHAR
	LogLog::error(LOG4CXX_STR("Can not activate ODBCAppender unless compiled with ODBC support."));
#else
	if (_priv->mappedName.empty())
	{
		LogLog::error(LOG4CXX_STR("ODBCAppender column mappings not defined, logging events will not be inserted"));
	}
	auto specs = getFormatSpecifiers();
	for (auto& name : _priv->mappedName)
	{
		auto lowerName = StringHelper::toLowerCase(name);
		auto pItem = specs.find(lowerName);
		if (specs.end() == pItem)
		{
			if (lowerName.size() < 5
			 || lowerName.substr(0, 4) != LOG4CXX_STR("mdc{"))
				LogLog::error(name + LOG4CXX_STR(" is not a supported ColumnMapping value"));
			else // A single MDC entry
			{
				auto index = lowerName.find(0x7D /* '}' */, 4);
				auto len = (lowerName.npos == index ? lowerName.size() : index) - 4;
				ODBCAppenderPriv::DataBinding paramData{ 0, 0, 0, 0, 0 };
				paramData.converter = std::make_shared<MDCPatternConverter>(lowerName.substr(4, len));
				_priv->parameterValue.push_back(paramData);
			}
		}
		else
		{
			ODBCAppenderPriv::DataBinding paramData{ 0, 0, 0, 0, 0 };
			std::vector<LogString> options;
			if (LOG4CXX_STR("time") == pItem->first)
				options.push_back(LOG4CXX_STR("yyyy-MM-dd HH:mm:ss.SSSSSS"));
			paramData.converter = LOG4CXX_NS::cast<LoggingEventPatternConverter>((pItem->second)(options));
			_priv->parameterValue.push_back(paramData);
		}
	}
#endif
}


void ODBCAppender::append( LOG4CXX_APPEND_FORMAL_PARAMETERS )
{
#if LOG4CXX_HAVE_ODBC && !LOG4CXX_LOGCHAR_IS_UNICHAR
	_priv->buffer.push_back(event);

	if (_priv->buffer.size() >= _priv->bufferSize)
	{
		flushBuffer(_priv->pool);
	}

#endif
}

#if LOG4CXX_ABI_VERSION <= 15
LogString ODBCAppender::getLogStatement(const spi::LoggingEventPtr& event, LOG4CXX_NS::helpers::Pool& p) const
{
    return LogString();
}

void ODBCAppender::execute(const LogString& sql, LOG4CXX_NS::helpers::Pool& p)
{
}
#endif

/* The default behavior holds a single connection open until the appender
is closed (typically when garbage collected).*/
void ODBCAppender::closeConnection(ODBCAppender::SQLHDBC /* con */)
{
}

ODBCAppender::SQLHDBC ODBCAppender::getConnection(LOG4CXX_NS::helpers::Pool& p)
{
#if LOG4CXX_HAVE_ODBC && !LOG4CXX_LOGCHAR_IS_UNICHAR
	SQLRETURN ret;

	if (_priv->env == SQL_NULL_HENV)
	{
		ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &_priv->env);

		if (ret < 0)
		{
			SQLException ex(SQL_HANDLE_ENV, _priv->env, "Failed to allocate SQL handle", p);
			_priv->env = SQL_NULL_HENV;
			throw ex;
		}

		ret = SQLSetEnvAttr(_priv->env, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_INTEGER);

		if (ret < 0)
		{
			SQLException ex(SQL_HANDLE_ENV, _priv->env, "Failed to set odbc version", p);
			SQLFreeHandle(SQL_HANDLE_ENV, _priv->env);
			_priv->env = SQL_NULL_HENV;
			throw ex;
		}
	}

	if (_priv->connection == SQL_NULL_HDBC)
	{
		ret = SQLAllocHandle(SQL_HANDLE_DBC, _priv->env, &_priv->connection);

		if (ret < 0)
		{
			SQLException ex(SQL_HANDLE_DBC, _priv->connection, "Failed to allocate sql handle", p);
			_priv->connection = SQL_NULL_HDBC;
			throw ex;
		}

#if LOG4CXX_LOGCHAR_IS_WCHAR
		SQLWCHAR *wUser = nullptr, *wPwd = nullptr;
		if (!_priv->databaseUser.empty())
			wUser = (SQLWCHAR*)_priv->databaseUser.c_str();
		if (!_priv->databasePassword.empty())
			wPwd = (SQLWCHAR*)_priv->databasePassword.c_str();
		ret = SQLConnectW(_priv->connection
			, (SQLWCHAR*)_priv->databaseURL.c_str(), SQL_NTS
			, wUser, SQL_NTS
			, wPwd, SQL_NTS
			);
#elif LOG4CXX_LOGCHAR_IS_UTF8
		SQLCHAR *wUser = nullptr, *wPwd = nullptr;
		if (!_priv->databaseUser.empty())
			wUser = (SQLCHAR*)_priv->databaseUser.c_str();
		if (!_priv->databasePassword.empty())
			wPwd = (SQLCHAR*)_priv->databasePassword.c_str();
		ret = SQLConnectA(_priv->connection
			, (SQLCHAR*)_priv->databaseURL.c_str(), SQL_NTS
			, wUser, SQL_NTS
			, wPwd, SQL_NTS
			);
#else
	#error ODBCAppender is not supported when logchar is unichar
#endif

		if (ret < 0)
		{
			SQLException ex(SQL_HANDLE_DBC, _priv->connection, "Failed to connect to database", p);
			SQLFreeHandle(SQL_HANDLE_DBC, _priv->connection);
			_priv->connection = SQL_NULL_HDBC;
			throw ex;
		}
	}

	return _priv->connection;
#else
	return 0;
#endif
}

void ODBCAppender::close()
{
	if (_priv->setClosed())
	{
#if LOG4CXX_HAVE_ODBC && !LOG4CXX_LOGCHAR_IS_UNICHAR
		if (!_priv->buffer.empty() && 0 == _priv->preparedStatement)
		{
			Pool p;
			_priv->setPreparedStatement(getConnection(p), p);
		}
#endif
		_priv->close();
	}
}

void ODBCAppender::ODBCAppenderPriv::close()
{
	try
	{
		Pool p;
		flushBuffer(p);
	}
	catch (SQLException& e)
	{
		this->errorHandler->error(LOG4CXX_STR("Error closing connection"),
			e, ErrorCode::GENERIC_FAILURE);
	}

#if LOG4CXX_HAVE_ODBC && !LOG4CXX_LOGCHAR_IS_UNICHAR

	if (this->connection != SQL_NULL_HDBC)
	{
		SQLDisconnect(this->connection);
		SQLFreeHandle(SQL_HANDLE_DBC, this->connection);
	}

	if (this->env != SQL_NULL_HENV)
	{
		SQLFreeHandle(SQL_HANDLE_ENV, this->env);
	}

#endif
}

#if LOG4CXX_HAVE_ODBC && !LOG4CXX_LOGCHAR_IS_UNICHAR
void ODBCAppender::ODBCAppenderPriv::setPreparedStatement(SQLHDBC con, Pool& p)
{
	auto ret = SQLAllocHandle( SQL_HANDLE_STMT, con, &this->preparedStatement);
	if (ret < 0)
	{
		throw SQLException( SQL_HANDLE_DBC, con, "Failed to allocate statement handle.", p);
	}

#if LOG4CXX_LOGCHAR_IS_WCHAR
	ret = SQLPrepareW(this->preparedStatement, (SQLWCHAR*)this->sqlStatement.c_str(), SQL_NTS);
#elif LOG4CXX_LOGCHAR_IS_UTF8
	ret = SQLPrepareA(this->preparedStatement, (SQLCHAR*)this->sqlStatement.c_str(), SQL_NTS);
#else
	#error ODBCAppender is not supported when logchar is unichar
#endif
	if (ret < 0)
	{
		throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to prepare sql statement.", p);
	}

	int parameterNumber = 0;
	for (auto& item : this->parameterValue)
	{
		++parameterNumber;
		SQLSMALLINT  targetType;
		SQLULEN      targetMaxCharCount;
		SQLSMALLINT  decimalDigits;
		SQLSMALLINT  nullable;
		auto ret = SQLDescribeParam
			( this->preparedStatement
			, parameterNumber
			, &targetType
			, &targetMaxCharCount
			, &decimalDigits
			, &nullable
			);
		if (ret < 0)
		{
			throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to describe parameter", p);
		}
		if (SQL_CHAR == targetType || SQL_VARCHAR == targetType || SQL_LONGVARCHAR == targetType)
		{
			item.paramType = SQL_C_CHAR;
			item.paramMaxCharCount = targetMaxCharCount;
			{
				size_t max_sz = (size_t)(std::numeric_limits<SQLINTEGER>::max)();
				size_t cnt = (size_t)(item.paramMaxCharCount);
				size_t max_chars = (max_sz - sizeof(char)) / sizeof(char);
				if (cnt > max_chars) cnt = max_chars;
				size_t bytes = cnt * sizeof(char) + sizeof(char);
				item.paramValueSize = (SQLINTEGER)bytes;
			}
			item.paramValue = (SQLPOINTER)this->pool.palloc(item.paramValueSize + sizeof(char));
		}
		else if (SQL_WCHAR == targetType || SQL_WVARCHAR == targetType || SQL_WLONGVARCHAR == targetType)
		{
			item.paramType = SQL_C_WCHAR;
			item.paramMaxCharCount = targetMaxCharCount;
			{
				size_t max_sz = (size_t)(std::numeric_limits<SQLINTEGER>::max)();
				size_t cnt = (size_t)(targetMaxCharCount);
				size_t max_chars = (max_sz - sizeof(wchar_t)) / sizeof(wchar_t);
				if (cnt > max_chars) cnt = max_chars;
				size_t bytes = cnt * sizeof(wchar_t) + sizeof(wchar_t);
				item.paramValueSize = (SQLINTEGER)bytes;
			}
			item.paramValue = (SQLPOINTER)this->pool.palloc(item.paramValueSize + sizeof(wchar_t));
		}
		else if (SQL_TYPE_TIMESTAMP == targetType || SQL_TYPE_DATE == targetType || SQL_TYPE_TIME == targetType
			|| SQL_DATETIME == targetType)
		{
			item.paramType = SQL_C_TYPE_TIMESTAMP;
			item.paramMaxCharCount = (0 <= decimalDigits) ? decimalDigits : 6;
			item.paramValueSize = sizeof(SQL_TIMESTAMP_STRUCT);
			item.paramValue = (SQLPOINTER)this->pool.palloc(item.paramValueSize);
		}
		else
		{
			if (SQL_INTEGER != targetType)
			{
				LogString msg(LOG4CXX_STR("Unexpected targetType ("));
				helpers::StringHelper::toString(targetType, msg);
				msg += LOG4CXX_STR(") at parameter ");
				helpers::StringHelper::toString(parameterNumber, msg);
				msg += LOG4CXX_STR(" while preparing SQL");
				LogLog::warn(msg);
			}
			item.paramMaxCharCount = 30;
#if LOG4CXX_LOGCHAR_IS_UTF8
			item.paramType = SQL_C_CHAR;
		{
			size_t max_sz = (size_t)(std::numeric_limits<SQLINTEGER>::max)();
			size_t cnt = (size_t)(item.paramMaxCharCount);
			size_t max_chars = (max_sz - sizeof(char)) / sizeof(char);
			if (cnt > max_chars) cnt = max_chars;
			size_t bytes = cnt * sizeof(char) + sizeof(char);
			item.paramValueSize = (SQLINTEGER)bytes;
		}
		item.paramValue = (SQLPOINTER)this->pool.palloc(item.paramValueSize + sizeof(char));
#else
			item.paramType = SQL_C_WCHAR;
		{
			size_t max_sz = (size_t)(std::numeric_limits<SQLINTEGER>::max)();
			size_t cnt = (size_t)(item.paramMaxCharCount);
			size_t max_chars = (max_sz - sizeof(wchar_t)) / sizeof(wchar_t);
			if (cnt > max_chars) cnt = max_chars;
			size_t bytes = cnt * sizeof(wchar_t) + sizeof(wchar_t);
			item.paramValueSize = (SQLINTEGER)bytes;
		}
		item.paramValue = (SQLPOINTER)this->pool.palloc(item.paramValueSize + sizeof(wchar_t));
#endif
		}
		item.strLen_or_Ind = SQL_NTS;
		ret = SQLBindParameter
			( this->preparedStatement
			, parameterNumber
			, SQL_PARAM_INPUT
			, item.paramType  // ValueType
			, targetType
			, targetMaxCharCount
			, decimalDigits
			, item.paramValue
			, item.paramValueSize
			, &item.strLen_or_Ind
			);
		if (ret < 0)
		{
			throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to bind parameter", p);
		}
	}
}

void ODBCAppender::ODBCAppenderPriv::setParameterValues(const spi::LoggingEventPtr& event, Pool& p)
{
	for (auto& item : this->parameterValue)
	{
		if (!item.paramValue || item.paramValueSize <= 0)
			;
		else if (SQL_C_WCHAR == item.paramType)
		{
			LogString sbuf;
			item.converter->format(event, sbuf, p);
#if LOG4CXX_LOGCHAR_IS_WCHAR
			std::wstring& tmp = sbuf;
#else
			std::wstring tmp;
			Transcoder::encode(sbuf, tmp);
#endif
			auto dst = (wchar_t*)item.paramValue;
			auto charCount = std::min(size_t(item.paramMaxCharCount), tmp.size());
			auto copySize = std::min(size_t(item.paramValueSize - 1), charCount * sizeof(wchar_t));
			std::memcpy(dst, tmp.data(), copySize);
			dst[copySize / sizeof(wchar_t)] = 0;
		}
		else if (SQL_C_CHAR == item.paramType)
		{
			LogString sbuf;
			item.converter->format(event, sbuf, p);
#if LOG4CXX_LOGCHAR_IS_UTF8
			std::string& tmp = sbuf;
#else
			std::string tmp;
			Transcoder::encode(sbuf, tmp);
#endif
			auto dst = (char*)item.paramValue;
			auto sz = std::min(size_t(item.paramMaxCharCount), tmp.size());
			auto copySize = std::min(size_t(item.paramValueSize - 1), sz * sizeof(char));
			std::memcpy(dst, tmp.data(), copySize);
			dst[copySize] = 0;
		}
		else if (SQL_C_TYPE_TIMESTAMP == item.paramType)
		{
			apr_time_exp_t exploded;
			apr_status_t stat = this->timeZone->explode(&exploded, event->getTimeStamp());
			if (stat == APR_SUCCESS)
			{
				auto dst = (SQL_TIMESTAMP_STRUCT*)item.paramValue;
				dst->year = 1900 + exploded.tm_year;
				dst->month = 1 + exploded.tm_mon;
				dst->day = exploded.tm_mday;
				dst->hour = exploded.tm_hour;
				dst->minute = exploded.tm_min;
				dst->second = exploded.tm_sec;
				// Prevent '[ODBC SQL Server Driver]Datetime field overflow' by rounding to the target field precision
				int roundingExponent = 6 - (int)item.paramMaxCharCount;
				if (0 < roundingExponent)
				{
					int roundingDivisor = (int)std::pow(10, roundingExponent);
					dst->fraction = 1000 * roundingDivisor * ((exploded.tm_usec + roundingDivisor / 2) / roundingDivisor);
				}
				else
					dst->fraction = 1000 * exploded.tm_usec;
			}
		}
	}
}
#endif

void ODBCAppender::flushBuffer(Pool& p)
{
#if LOG4CXX_HAVE_ODBC && !LOG4CXX_LOGCHAR_IS_UNICHAR
	if (0 == _priv->preparedStatement)
		_priv->setPreparedStatement(getConnection(p), p);
	_priv->flushBuffer(p);
#endif
}

void ODBCAppender::ODBCAppenderPriv::flushBuffer(Pool& p)
{
	if (0 == this->preparedStatement)
		;
	else for (auto& logEvent : this->buffer)
	{
		if (this->parameterValue.empty())
			this->errorHandler->error(LOG4CXX_STR("ODBCAppender column mappings not defined"));
#if LOG4CXX_HAVE_ODBC && !LOG4CXX_LOGCHAR_IS_UNICHAR
		else try
		{
			this->setParameterValues(logEvent, p);
			auto ret = SQLExecute(this->preparedStatement);
			if (ret < 0)
			{
				throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to execute prepared statement", p);
			}
		}
		catch (SQLException& e)
		{
			this->errorHandler->error(LOG4CXX_STR("Failed to execute sql"), e,
				ErrorCode::FLUSH_FAILURE);
		}
#endif
	}

	// clear the buffer of reported events
	this->buffer.clear();
}

void ODBCAppender::setSql(const LogString& s)
{
	const logchar doubleQuote{ 0x22 };
	const logchar singleQuote{ 0x27 };
	const logchar semiColan{ 0x3b };
	// A basic check which disallows multiple SQL statements - for defense-in-depth security.
	// Allow a semicolan in a quoted context or as the last character.
	logchar currentQuote{ 0 };
	int charCount{ 0 };
	for (auto ch : s)
	{
		++charCount;
		if (currentQuote == ch)
			currentQuote = 0;
		else if (currentQuote == 0)
		{
			if (doubleQuote == ch || singleQuote == ch)
				currentQuote = ch;
			else if (semiColan == ch && s.size() != charCount)
				throw IllegalArgumentException(LOG4CXX_STR("SQL statement cannot contain a ';'"));
		}
	}
	if (0 != currentQuote)
		throw IllegalArgumentException(LogString(LOG4CXX_STR("Unmatched ")) + currentQuote + LOG4CXX_STR(" in SQL statement"));
    _priv->sqlStatement = s;
}

const LogString& ODBCAppender::getSql() const
{
	return _priv->sqlStatement;
}

void ODBCAppender::setUser(const LogString& user)
{
	_priv->databaseUser = user;
}

void ODBCAppender::setURL(const LogString& url)
{
	_priv->databaseURL = url;
}

void ODBCAppender::setPassword(const LogString& password)
{
	_priv->databasePassword = password;
}

void ODBCAppender::setBufferSize(size_t newBufferSize)
{
	_priv->bufferSize = newBufferSize;
}

const LogString& ODBCAppender::getUser() const
{
	return _priv->databaseUser;
}

const LogString& ODBCAppender::getURL() const
{
	return _priv->databaseURL;
}

const LogString& ODBCAppender::getPassword() const
{
	return _priv->databasePassword;
}

size_t ODBCAppender::getBufferSize() const
{
	return _priv->bufferSize;
}

