You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by sw...@apache.org on 2026/10/23 03:10:30 UTC

(logging-log4cxx) 01/01: Allow ODBCAppender testing with change code

This is an automated email from the ASF dual-hosted git repository.

swebb2066 pushed a commit to branch simplify_odbc_appender_interface
in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git

commit f06c6c5b28dc27eeb726ed40d2ff772b51b5fab5
Author: Stephen Webb <sw...@gmail.com>
AuthorDate: Fri Oct 23 14:10:06 2026 +1100

    Allow ODBCAppender testing with change code
---
 src/main/cpp/odbcappender.cpp                      | 102 ++++++++++-----------
 src/main/include/log4cxx/db/odbcappender.h         |  40 ++++----
 .../include/log4cxx/private/odbcappender_priv.h    |   6 +-
 src/test/cpp/db/odbcappendertestcase.cpp           |  58 ++++++++++--
 .../odbcAppenderDSN-Log4cxxSQLiteTest.properties   |   2 +
 .../input/xml/odbcAppenderDSN-Log4cxxTest.xml      |   5 +-
 6 files changed, 129 insertions(+), 84 deletions(-)

diff --git a/src/main/cpp/odbcappender.cpp b/src/main/cpp/odbcappender.cpp
index 0be160b5..ccffa2e5 100644
--- a/src/main/cpp/odbcappender.cpp
+++ b/src/main/cpp/odbcappender.cpp
@@ -65,29 +65,22 @@ 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
+	( short            fHandleType
+	, void*            hInput
+	, const LogString& context
+	)
+	: Exception(formatMessage(fHandleType, hInput, context))
 {
 }
 
-
-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)
+const LogString SQLException::formatMessage
+	( short            fHandleType
+	, void*            hInput
+	, const LogString& context
+	)
 {
-	std::string strReturn(prolog);
-	strReturn.append(" - ");
+	LogString result;
 #if LOG4CXX_HAVE_ODBC
 	SQLCHAR       SqlState[6];
 	SQLCHAR       Msg[SQL_MAX_MESSAGE_LENGTH];
@@ -102,15 +95,16 @@ const char* SQLException::formatMessage(short fHandleType,
 	while ((rc2 = SQLGetDiagRecA(fHandleType, hInput, i, SqlState, &NativeError,
 					Msg, sizeof(Msg), &MsgLen)) != SQL_NO_DATA)
 	{
-		strReturn.append((char*) Msg);
+		helpers::Transcoder::decode((char*)Msg, result);
 		i++;
 	}
-
+	if (1 < i)
+		result.append(LOG4CXX_STR(" - "));
+	result.append(context);
 #else
-	strReturn.append("log4cxx built without ODBC support");
+	result.append(LOG4CXX_STR("log4cxx built without ODBC support"));
 #endif
-
-	return apr_pstrdup((apr_pool_t*) p.getAPRPool(), strReturn.c_str());
+	return result;
 }
 
 
@@ -129,7 +123,7 @@ ODBCAppender::ODBCAppender()
 			{
 				flushBuffer(_priv->pool);
 			}
-			catch (SQLException& e)
+			catch (const SQLException& e)
 			{
 				_priv->errorHandler->error(LOG4CXX_STR("Error flushing connection"),
 					e, ErrorCode::GENERIC_FAILURE);
@@ -267,7 +261,7 @@ void ODBCAppender::append( const spi::LoggingEventPtr& event )
 
 	if (_priv->buffer.size() >= _priv->bufferSize)
 	{
-		flushBuffer(_priv->pool);
+		flushBuffer();
 	}
 
 #endif
@@ -280,7 +274,7 @@ void ODBCAppender::closeConnection(ODBCAppender::SQLHDBC /* con */)
 {
 }
 
-ODBCAppender::SQLHDBC ODBCAppender::getConnection(LOG4CXX_NS::helpers::Pool& p)
+ODBCAppender::SQLHDBC ODBCAppender::getConnection()
 {
 #if LOG4CXX_HAVE_ODBC
 	SQLRETURN ret = -1;
@@ -291,7 +285,7 @@ ODBCAppender::SQLHDBC ODBCAppender::getConnection(LOG4CXX_NS::helpers::Pool& p)
 
 		if (ret < 0)
 		{
-			SQLException ex(SQL_HANDLE_ENV, _priv->env, "Failed to allocate SQL handle", p);
+			SQLException ex(SQL_HANDLE_ENV, _priv->env, LOG4CXX_STR("Failed to allocate SQL handle"));
 			_priv->env = SQL_NULL_HENV;
 			throw ex;
 		}
@@ -300,7 +294,7 @@ ODBCAppender::SQLHDBC ODBCAppender::getConnection(LOG4CXX_NS::helpers::Pool& p)
 
 		if (ret < 0)
 		{
-			SQLException ex(SQL_HANDLE_ENV, _priv->env, "Failed to set odbc version", p);
+			SQLException ex(SQL_HANDLE_ENV, _priv->env, LOG4CXX_STR("Failed to set odbc version"));
 			SQLFreeHandle(SQL_HANDLE_ENV, _priv->env);
 			_priv->env = SQL_NULL_HENV;
 			throw ex;
@@ -313,7 +307,7 @@ ODBCAppender::SQLHDBC ODBCAppender::getConnection(LOG4CXX_NS::helpers::Pool& p)
 
 		if (ret < 0)
 		{
-			SQLException ex(SQL_HANDLE_DBC, _priv->connection, "Failed to allocate sql handle", p);
+			SQLException ex(SQL_HANDLE_DBC, _priv->connection, LOG4CXX_STR("Failed to allocate sql handle"));
 			_priv->connection = SQL_NULL_HDBC;
 			throw ex;
 		}
@@ -344,7 +338,7 @@ ODBCAppender::SQLHDBC ODBCAppender::getConnection(LOG4CXX_NS::helpers::Pool& p)
 
 		if (ret < 0)
 		{
-			SQLException ex(SQL_HANDLE_DBC, _priv->connection, "Failed to connect to database", p);
+			SQLException ex(SQL_HANDLE_DBC, _priv->connection, LOG4CXX_STR("from SQLConect using ") + _priv->databaseURL);
 			SQLFreeHandle(SQL_HANDLE_DBC, _priv->connection);
 			_priv->connection = SQL_NULL_HDBC;
 			throw ex;
@@ -361,14 +355,19 @@ void ODBCAppender::close()
 {
 	if (_priv->setClosed())
 	{
+		try
+		{
 #if LOG4CXX_HAVE_ODBC
-		if (!_priv->buffer.empty() && 0 == _priv->preparedStatement)
+			if (!_priv->buffer.empty() && 0 == _priv->preparedStatement)
+				_priv->setPreparedStatement(getConnection());
+#endif
+			_priv->close();
+		}
+		catch (const SQLException& e)
 		{
-			Pool p;
-			_priv->setPreparedStatement(getConnection(p), p);
+			_priv->errorHandler->error(LOG4CXX_STR("Error closing connection"),
+				e, ErrorCode::GENERIC_FAILURE);
 		}
-#endif
-		_priv->close();
 	}
 }
 
@@ -376,10 +375,9 @@ void ODBCAppender::ODBCAppenderPriv::close()
 {
 	try
 	{
-		Pool p;
-		flushBuffer(p);
+		flushBuffer();
 	}
-	catch (SQLException& e)
+	catch (const SQLException& e)
 	{
 		this->errorHandler->error(LOG4CXX_STR("Error closing connection"),
 			e, ErrorCode::GENERIC_FAILURE);
@@ -402,12 +400,12 @@ void ODBCAppender::ODBCAppenderPriv::close()
 }
 
 #if LOG4CXX_HAVE_ODBC
-void ODBCAppender::ODBCAppenderPriv::setPreparedStatement(SQLHDBC con, Pool& p)
+void ODBCAppender::ODBCAppenderPriv::setPreparedStatement(SQLHDBC con)
 {
 	auto ret = SQLAllocHandle( SQL_HANDLE_STMT, con, &this->preparedStatement);
 	if (ret < 0)
 	{
-		throw SQLException( SQL_HANDLE_DBC, con, "Failed to allocate statement handle.", p);
+		throw SQLException( SQL_HANDLE_DBC, con, LOG4CXX_STR("Failed to allocate statement handle."));
 	}
 
 #if LOG4CXX_LOGCHAR_IS_WCHAR
@@ -417,7 +415,7 @@ void ODBCAppender::ODBCAppenderPriv::setPreparedStatement(SQLHDBC con, Pool& p)
 #endif
 	if (ret < 0)
 	{
-		throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to prepare sql statement.", p);
+		throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, LOG4CXX_STR("Failed to prepare sql statement."));
 	}
 
 	int parameterNumber = 0;
@@ -438,7 +436,7 @@ void ODBCAppender::ODBCAppenderPriv::setPreparedStatement(SQLHDBC con, Pool& p)
 			);
 		if (ret < 0)
 		{
-			throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to describe parameter", p);
+			throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, LOG4CXX_STR("Failed to describe parameter"));
 		}
 		if (SQL_CHAR == targetType || SQL_VARCHAR == targetType || SQL_LONGVARCHAR == targetType)
 		{
@@ -527,12 +525,12 @@ void ODBCAppender::ODBCAppenderPriv::setPreparedStatement(SQLHDBC con, Pool& p)
 			);
 		if (ret < 0)
 		{
-			throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to bind parameter", p);
+			throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, LOG4CXX_STR("Failed to bind parameter"));
 		}
 	}
 }
 
-void ODBCAppender::ODBCAppenderPriv::setParameterValues(const spi::LoggingEventPtr& event, Pool& p)
+void ODBCAppender::ODBCAppenderPriv::setParameterValues(const spi::LoggingEventPtr& event)
 {
 	for (auto& item : this->parameterValue)
 	{
@@ -598,16 +596,16 @@ void ODBCAppender::ODBCAppenderPriv::setParameterValues(const spi::LoggingEventP
 }
 #endif
 
-void ODBCAppender::flushBuffer(Pool& p)
+void ODBCAppender::flushBuffer()
 {
 #if LOG4CXX_HAVE_ODBC
 	if (0 == _priv->preparedStatement)
-		_priv->setPreparedStatement(getConnection(p), p);
-	_priv->flushBuffer(p);
+		_priv->setPreparedStatement(getConnection());
+	_priv->flushBuffer();
 #endif
 }
 
-void ODBCAppender::ODBCAppenderPriv::flushBuffer(Pool& p)
+void ODBCAppender::ODBCAppenderPriv::flushBuffer()
 {
 	if (0 == this->preparedStatement)
 		;
@@ -618,14 +616,14 @@ void ODBCAppender::ODBCAppenderPriv::flushBuffer(Pool& p)
 #if LOG4CXX_HAVE_ODBC
 		else try
 		{
-			this->setParameterValues(logEvent, p);
+			this->setParameterValues(logEvent);
 			auto ret = SQLExecute(this->preparedStatement);
 			if (ret < 0)
 			{
-				throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to execute prepared statement", p);
+				throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, LOG4CXX_STR("Failed to execute prepared statement"));
 			}
 		}
-		catch (SQLException& e)
+		catch (const SQLException& e)
 		{
 			this->errorHandler->error(LOG4CXX_STR("Failed to execute sql"), e,
 				ErrorCode::FLUSH_FAILURE);
diff --git a/src/main/include/log4cxx/db/odbcappender.h b/src/main/include/log4cxx/db/odbcappender.h
index fcbd09eb..df8e5be1 100644
--- a/src/main/include/log4cxx/db/odbcappender.h
+++ b/src/main/include/log4cxx/db/odbcappender.h
@@ -37,16 +37,18 @@ namespace db
 {
 class LOG4CXX_EXPORT SQLException : public LOG4CXX_NS::helpers::Exception
 {
-	public:
-		SQLException(short fHandleType,
-			void* hInput, const char* prolog,
-			LOG4CXX_NS::helpers::Pool& p);
-		SQLException(const char* msg);
-		SQLException(const SQLException& src);
-	private:
-		const char* formatMessage(short fHandleType,
-			void* hInput, const char* prolog,
-			LOG4CXX_NS::helpers::Pool& p);
+public:
+	SQLException
+		( short            fHandleType
+		, void*            hInput
+		, const LogString& context
+		);
+private:
+	const LogString formatMessage
+		( short            fHandleType
+		, void*            hInput
+		, const LogString& context
+		);
 };
 
 /**
@@ -202,14 +204,7 @@ class LOG4CXX_EXPORT ODBCAppender : public AppenderSkeleton
 		* By default this creates a single connection which is held open
 		* until the object is garbage collected.
 		*/
-		virtual SQLHDBC getConnection(LOG4CXX_NS::helpers::Pool& p) /*throw(SQLException)*/;
-
-		/**
-		* Closes the appender, flushing the buffer first then closing the default
-		* connection if it is open.
-		*/
-	public:
-		void close() override;
+		virtual SQLHDBC getConnection() /*throw(SQLException)*/;
 
 		/**
 		* loops through the buffer of LoggingEvents, gets a
@@ -218,7 +213,14 @@ class LOG4CXX_EXPORT ODBCAppender : public AppenderSkeleton
 		*
 		* If a statement fails the LoggingEvent stays in the buffer!
 		*/
-		virtual void flushBuffer(LOG4CXX_NS::helpers::Pool& p);
+		virtual void flushBuffer();
+
+	public:
+		/**
+		* Closes the appender, flushing the buffer first then closing the default
+		* connection if it is open.
+		*/
+		void close() override;
 
 		/**
 		* Does this appender require a layout?
diff --git a/src/main/include/log4cxx/private/odbcappender_priv.h b/src/main/include/log4cxx/private/odbcappender_priv.h
index f46c614b..d07cadd2 100644
--- a/src/main/include/log4cxx/private/odbcappender_priv.h
+++ b/src/main/include/log4cxx/private/odbcappender_priv.h
@@ -128,15 +128,15 @@ struct ODBCAppender::ODBCAppenderPriv : public AppenderSkeleton::AppenderSkeleto
 	std::vector<LogString>   mappedName;
 	std::vector<DataBinding> parameterValue;
 #if LOG4CXX_HAVE_ODBC
-	void setPreparedStatement(SQLHDBC con, helpers::Pool& p);
-	void setParameterValues(const spi::LoggingEventPtr& event, helpers::Pool& p);
+	void setPreparedStatement(SQLHDBC con);
+	void setParameterValues(const spi::LoggingEventPtr& event);
 #endif
 
 #if LOG4CXX_EVENTS_AT_EXIT
 	helpers::AtExitRegistry::Raii atExitRegistryRaii;
 #endif
 
-	void flushBuffer(helpers::Pool& p);
+	void flushBuffer();
 	void close();
 };
 
diff --git a/src/test/cpp/db/odbcappendertestcase.cpp b/src/test/cpp/db/odbcappendertestcase.cpp
index 040be189..45d5958c 100644
--- a/src/test/cpp/db/odbcappendertestcase.cpp
+++ b/src/test/cpp/db/odbcappendertestcase.cpp
@@ -18,6 +18,10 @@
 #include <log4cxx/logmanager.h>
 #include <log4cxx/db/odbcappender.h>
 #include <log4cxx/xml/domconfigurator.h>
+#include <log4cxx/helpers/fileinputstream.h>
+#include <log4cxx/helpers/loglog.h>
+#include <log4cxx/helpers/properties.h>
+#include <log4cxx/helpers/system.h>
 #include "../appenderskeletontestcase.h"
 #include <apr_time.h>
 
@@ -40,10 +44,7 @@ class ODBCAppenderTestCase : public AppenderSkeletonTestCase
 		LOGUNIT_TEST(testDefaultThreshold);
 		LOGUNIT_TEST(testSetOptionThreshold);
 		LOGUNIT_TEST(testNegativeBufferSizeOption);
-//#define DataSourceName_Log4cxxTest_Is_Valid
-#ifdef DataSourceName_Log4cxxTest_Is_Valid
 		LOGUNIT_TEST(testConnectUsingDSN);
-#endif
 		LOGUNIT_TEST_SUITE_END();
 
 
@@ -67,11 +68,25 @@ class ODBCAppenderTestCase : public AppenderSkeletonTestCase
 			LOGUNIT_ASSERT_EQUAL((size_t) 1, appender.getBufferSize());
 		}
 
-// 'odbcAppenderDSN-Log4cxxTest.xml' requires the data souce name 'Log4cxxTest'
-// containing a 'ApplicationLogs' database
-// with 'UnitTestLog' table
-// containing the fields shown below:
+// testConnectUsingDSN requires the environment variable LOG4CXX_ODBC_TEST_DATA_FILE_PATH
+// that names a properties file that has entries specifiing
+// the data souce name (e.g. Log4cxxTest) identifying a database
+// and a table name (e.g. UnitTestLog )
+// where the table contains the fields shown
+// in the following SQL data definition language statements:
+// SQLite:
+// CREATE TABLE UnitTestLog(
+//     Item INTEGER PRIMARY KEY AUTOINCREMENT,
+//     Thread VARCHAR(20),
+//     LogTime DATETIME NOT NULL,
+//     LogName VARCHAR(50),
+//     LogLevel VARCHAR(10),
+//     FileName VARCHAR(300),
+//     FileLine INTEGER,
+//     Message VARCHAR(1000)
+// );
 //
+// MSSQLServer:
 // USE [ApplicationLogs]
 // GO
 //
@@ -95,6 +110,35 @@ class ODBCAppenderTestCase : public AppenderSkeletonTestCase
 //
 		void testConnectUsingDSN()
 		{
+			LogString odbcDataFileNameVar = LOG4CXX_STR("LOG4CXX_ODBC_TEST_DATA_FILE_PATH");
+			auto odbcDataFileName = helpers::System::getProperty(odbcDataFileNameVar);
+			if (odbcDataFileName.empty())
+			{
+				helpers::LogLog::warn(
+					LOG4CXX_STR("Set the ") + odbcDataFileNameVar + LOG4CXX_STR(" environment variable"
+					" and re-run testConnectUsingDSN to initialise it with the required values")
+					);
+				return;
+			}
+			File odbcDataFile(odbcDataFileName);
+			if (!odbcDataFile.exists())
+			{
+				std::ofstream f(odbcDataFileName);
+				const char odbcData[] =
+					"LOG4CXX_TEST_ODBC_DSN=\n"
+					"LOG4CXX_TEST_ODBC_TABLE_NAME=\n"
+					;
+				f.write(odbcData, sizeof (odbcData));
+				f.close();
+				LOGUNIT_ASSERT(odbcDataFile.exists());
+				helpers::LogLog::warn(LOG4CXX_STR("Edit values into '") + odbcDataFileName + LOG4CXX_STR("' and re-run testConnectUsingDSN"));
+				return;
+			}
+			auto odbcDataStream = std::make_shared<helpers::FileInputStream>(odbcDataFile);
+			helpers::Properties& odbcData = spi::Configurator::properties();
+			odbcData.load(odbcDataStream);
+			LOGUNIT_ASSERT(!odbcData.getProperty(LOG4CXX_STR("LOG4CXX_TEST_ODBC_DSN")).empty());
+			LOGUNIT_ASSERT(!odbcData.getProperty(LOG4CXX_STR("LOG4CXX_TEST_ODBC_TABLE_NAME")).empty());
 			auto status = xml::DOMConfigurator::configure("input/xml/odbcAppenderDSN-Log4cxxTest.xml");
 			LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);
 			auto odbc = Logger::getLogger("DB.UnitTest");
diff --git a/src/test/resources/input/xml/odbcAppenderDSN-Log4cxxSQLiteTest.properties b/src/test/resources/input/xml/odbcAppenderDSN-Log4cxxSQLiteTest.properties
new file mode 100644
index 00000000..62ab4976
--- /dev/null
+++ b/src/test/resources/input/xml/odbcAppenderDSN-Log4cxxSQLiteTest.properties
@@ -0,0 +1,2 @@
+LOG4CXX_TEST_ODBC_DSN=Log4cxxSQLiteTest
+LOG4CXX_TEST_ODBC_TABLE_NAME=UnitTestLog
\ No newline at end of file
diff --git a/src/test/resources/input/xml/odbcAppenderDSN-Log4cxxTest.xml b/src/test/resources/input/xml/odbcAppenderDSN-Log4cxxTest.xml
index 04e278da..3e947e27 100644
--- a/src/test/resources/input/xml/odbcAppenderDSN-Log4cxxTest.xml
+++ b/src/test/resources/input/xml/odbcAppenderDSN-Log4cxxTest.xml
@@ -26,9 +26,8 @@
   </layout>
 </appender>
 <appender name="PreparedAppender" class="ODBCAppender">
- <param name="DSN" value="Log4cxxTest"/>
- <!--param name="sql" value="INSERT INTO UnitTestLog (Thread,LogName,LogTime,LogLevel,FileName,FileLine,Message) VALUES (?,?,?,?,?,?,?)" /-->
- <param name="sql" value="INSERT INTO ApplicationLogs.dbo.UnitTestLog (Thread,LogName,LogTime,LogLevel,FileName,FileLine,Message) VALUES (?,?,?,?,?,?,?)" />
+ <param name="DSN" value="${LOG4CXX_TEST_ODBC_DSN}"/>
+  <param name="sql" value="INSERT INTO ${LOG4CXX_TEST_ODBC_TABLE_NAME} (Thread,LogName,LogTime,LogLevel,FileName,FileLine,Message) VALUES (?,?,?,?,?,?,?)" />
  <param name="ColumnMapping" value="thread"/>
  <param name="ColumnMapping" value="logger"/>
  <param name="ColumnMapping" value="time"/>