| /* | |
| * 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 | |
| #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 | |
| 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( ) | |
| { | |
| #if !LOG4CXX_HAVE_ODBC | |
| 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( const spi::LoggingEventPtr& event ) | |
| { | |
| #if LOG4CXX_HAVE_ODBC | |
| _priv->buffer.push_back(event); | |
| if (_priv->buffer.size() >= _priv->bufferSize) | |
| { | |
| flushBuffer(_priv->pool); | |
| } | |
| #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 | |
| SQLRETURN ret = -1; | |
| 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 | |
| ); | |
| #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 | |
| 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 | |
| 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 | |
| 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); | |
| #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); | |
| #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); | |
| #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 | |
| 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 | |
| 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; | |
| } | |