Bläddra i källkod

fix(Poco::Data): fixes and improvements #4198 (#4199)

* fix(Poco::Data): fixes and improvements #4198

* chore: remove inadvertently commited garbage file

* fix(SQLite): SQLChannel tests #4198

* fix(Data::SessionPool): Improve Data::SessionPool thread safety #4206
Aleksandar Fabijanic 2 år sedan
förälder
incheckning
5131fe1c15
51 ändrade filer med 2468 tillägg och 787 borttagningar
  1. 39 36
      Data/ODBC/include/Poco/Data/ODBC/Binder.h
  2. 49 6
      Data/ODBC/include/Poco/Data/ODBC/ConnectionHandle.h
  3. 2 0
      Data/ODBC/include/Poco/Data/ODBC/Diagnostics.h
  4. 0 1
      Data/ODBC/include/Poco/Data/ODBC/EnvironmentHandle.h
  5. 3 1
      Data/ODBC/include/Poco/Data/ODBC/Extractor.h
  6. 23 11
      Data/ODBC/include/Poco/Data/ODBC/ODBCStatementImpl.h
  7. 4 4
      Data/ODBC/include/Poco/Data/ODBC/Preparator.h
  8. 27 11
      Data/ODBC/include/Poco/Data/ODBC/SessionImpl.h
  9. 2 2
      Data/ODBC/include/Poco/Data/ODBC/TypeInfo.h
  10. 52 46
      Data/ODBC/src/Binder.cpp
  11. 117 16
      Data/ODBC/src/ConnectionHandle.cpp
  12. 15 10
      Data/ODBC/src/EnvironmentHandle.cpp
  13. 10 9
      Data/ODBC/src/Extractor.cpp
  14. 3 3
      Data/ODBC/src/ODBCMetaColumn.cpp
  15. 86 29
      Data/ODBC/src/ODBCStatementImpl.cpp
  16. 1 1
      Data/ODBC/src/Parameter.cpp
  17. 4 3
      Data/ODBC/src/Preparator.cpp
  18. 98 62
      Data/ODBC/src/SessionImpl.cpp
  19. 6 5
      Data/ODBC/src/TypeInfo.cpp
  20. 3 0
      Data/ODBC/testsuite/src/ODBCDB2Test.cpp
  21. 3 0
      Data/ODBC/testsuite/src/ODBCMySQLTest.cpp
  22. 3 0
      Data/ODBC/testsuite/src/ODBCOracleTest.cpp
  23. 3 0
      Data/ODBC/testsuite/src/ODBCPostgreSQLTest.cpp
  24. 399 285
      Data/ODBC/testsuite/src/ODBCSQLServerTest.cpp
  25. 9 7
      Data/ODBC/testsuite/src/ODBCSQLServerTest.h
  26. 3 0
      Data/ODBC/testsuite/src/ODBCSQLiteTest.cpp
  27. 34 1
      Data/ODBC/testsuite/src/ODBCTest.cpp
  28. 7 1
      Data/ODBC/testsuite/src/ODBCTest.h
  29. 648 21
      Data/ODBC/testsuite/src/SQLExecutor.cpp
  30. 15 3
      Data/ODBC/testsuite/src/SQLExecutor.h
  31. 0 5
      Data/SQLite/src/SessionImpl.cpp
  32. 1 0
      Data/SQLite/src/Utility.cpp
  33. 80 45
      Data/SQLite/testsuite/src/SQLiteTest.cpp
  34. 20 0
      Data/include/Poco/Data/AbstractSessionImpl.h
  35. 2 1
      Data/include/Poco/Data/ArchiveStrategy.h
  36. 2 0
      Data/include/Poco/Data/PooledSessionImpl.h
  37. 2 1
      Data/include/Poco/Data/Preparation.h
  38. 3 0
      Data/include/Poco/Data/RecordSet.h
  39. 141 49
      Data/include/Poco/Data/SQLChannel.h
  40. 17 0
      Data/include/Poco/Data/Session.h
  41. 11 0
      Data/include/Poco/Data/SessionImpl.h
  42. 15 15
      Data/include/Poco/Data/SessionPool.h
  43. 10 0
      Data/include/Poco/Data/Statement.h
  44. 16 4
      Data/include/Poco/Data/Transaction.h
  45. 3 1
      Data/src/ArchiveStrategy.cpp
  46. 12 0
      Data/src/PooledSessionImpl.cpp
  47. 8 1
      Data/src/RecordSet.cpp
  48. 419 67
      Data/src/SQLChannel.cpp
  49. 15 19
      Data/src/SessionPool.cpp
  50. 19 2
      Data/src/Transaction.cpp
  51. 4 3
      build/config/Linux

+ 39 - 36
Data/ODBC/include/Poco/Data/ODBC/Binder.h

@@ -407,7 +407,7 @@ private:
 			decDigits,
 			(SQLPOINTER) &val, 0, 0)))
 		{
-			throw StatementException(_rStmt, "SQLBindParameter()");
+			throw StatementException(_rStmt, "ODBC::Binder::SQLBindParameter()");
 		}
 	}
 
@@ -436,14 +436,14 @@ private:
 			(SQLUSMALLINT) pos + 1,
 			SQL_PARAM_INPUT,
 			SQL_C_BINARY,
-			SQL_LONGVARBINARY,
+			Utility::sqlDataType(SQL_C_BINARY),
 			columnSize,
 			0,
 			pVal,
 			(SQLINTEGER) size,
 			_lengthIndicator.back())))
 		{
-			throw StatementException(_rStmt, "SQLBindParameter(LOB)");
+			throw StatementException(_rStmt, "ODBC::Binder::SQLBindParameter(LOB)");
 		}
 	}
 
@@ -476,7 +476,7 @@ private:
 			0,
 			&(*_vecLengthIndicator[pos])[0])))
 		{
-			throw StatementException(_rStmt, "SQLBindParameter()");
+			throw StatementException(_rStmt, "ODBC::Binder::SQLBindParameter()");
 		}
 	}
 
@@ -537,7 +537,7 @@ private:
 			0,
 			&(*_vecLengthIndicator[pos])[0])))
 		{
-			throw StatementException(_rStmt, "SQLBindParameter()");
+			throw StatementException(_rStmt, "ODBC::Binder::SQLBindParameter()");
 		}
 	}
 
@@ -577,7 +577,7 @@ private:
 		if (size == _maxFieldSize)
 		{
 			getMinValueSize(*pVal, size);
-			// accomodate for terminating zero
+			// accommodate for terminating zero
 			if (size != _maxFieldSize) ++size;
 		}
 
@@ -614,14 +614,14 @@ private:
 			(SQLUSMALLINT) pos + 1,
 			toODBCDirection(dir),
 			SQL_C_CHAR,
-			SQL_LONGVARCHAR,
+			Utility::sqlDataType(SQL_C_CHAR),
 			(SQLUINTEGER) size - 1,
 			0,
 			_charPtrs[pos],
 			(SQLINTEGER) size,
 			&(*_vecLengthIndicator[pos])[0])))
 		{
-			throw StatementException(_rStmt, Poco::format("SQLBindParameter(%s)", typeID));
+			throw StatementException(_rStmt, "SQLBindParameter(std::vector<std::string>)");
 		}
 	}
 
@@ -672,7 +672,7 @@ private:
 		{
 			strSize = it->size() * sizeof(UTF16Char);
 			if (strSize > size)
-				throw LengthExceededException("SQLBindParameter(std::vector<UTF16String>)");
+				throw LengthExceededException("ODBC::Binder::bindImplContainerUTF16String:SQLBindParameter(std::vector<UTF16String>)");
 			std::memcpy(pBuf + offset, it->data(), strSize);
 			offset += size;
 		}
@@ -681,14 +681,14 @@ private:
 			(SQLUSMALLINT)pos + 1,
 			toODBCDirection(dir),
 			SQL_C_WCHAR,
-			SQL_WLONGVARCHAR,
+			Utility::sqlDataType(SQL_C_WCHAR),
 			(SQLUINTEGER)size - 1,
 			0,
 			_utf16CharPtrs[pos],
 			(SQLINTEGER)size,
 			&(*_vecLengthIndicator[pos])[0])))
 		{
-			throw StatementException(_rStmt, "SQLBindParameter(std::vector<UTF16String>)");
+			throw StatementException(_rStmt, "ODBC::Binder::bindImplContainerUTF16String:SQLBindParameter(std::vector<UTF16String>)");
 		}
 	}
 
@@ -699,14 +699,14 @@ private:
 		typedef typename LOBType::ValueType CharType;
 
 		if (isOutBound(dir) || !isInBound(dir))
-			throw NotImplementedException("BLOB container parameter type can only be inbound.");
+			throw NotImplementedException("ODBC::Binder::bindImplContainerLOB():BLOB container parameter type can only be inbound.");
 
 		if (PB_IMMEDIATE != _paramBinding)
-			throw InvalidAccessException("Containers can only be bound immediately.");
+			throw InvalidAccessException("ODBC::Binder::bindImplContainerLOB():Containers can only be bound immediately.");
 
 		std::size_t length = val.size();
 		if (0 == length)
-			throw InvalidArgumentException("Empty container not allowed.");
+			throw InvalidArgumentException("ODBC::Binder::bindImplContainerLOB():Empty container not allowed.");
 
 		setParamSetSize(length);
 
@@ -742,7 +742,7 @@ private:
 		{
 			blobSize = cIt->size();
 			if (blobSize > size)
-				throw LengthExceededException("SQLBindParameter(std::vector<BLOB>)");
+				throw LengthExceededException("ODBC::Binder::bindImplContainerLOB():SQLBindParameter(std::vector<BLOB>)");
 			std::memcpy(_charPtrs[pos] + offset, cIt->rawContent(), blobSize * sizeof(CharType));
 			offset += size;
 		}
@@ -751,14 +751,14 @@ private:
 			(SQLUSMALLINT) pos + 1,
 			SQL_PARAM_INPUT,
 			SQL_C_BINARY,
-			SQL_LONGVARBINARY,
+			Utility::sqlDataType(SQL_C_BINARY),
 			(SQLUINTEGER) size,
 			0,
 			_charPtrs[pos],
 			(SQLINTEGER) size,
 			&(*_vecLengthIndicator[pos])[0])))
 		{
-			throw StatementException(_rStmt, "SQLBindParameter(std::vector<BLOB>)");
+			throw StatementException(_rStmt, "ODBC::Binder::bindImplContainerLOB():SQLBindParameter(std::vector<BLOB>)");
 		}
 	}
 
@@ -766,15 +766,15 @@ private:
 	void bindImplContainerDate(std::size_t pos, const C& val, Direction dir)
 	{
 		if (isOutBound(dir) || !isInBound(dir))
-			throw NotImplementedException("Date vector parameter type can only be inbound.");
+			throw NotImplementedException("ODBC::Binder::bindImplContainerDate():Date vector parameter type can only be inbound.");
 
 		if (PB_IMMEDIATE != _paramBinding)
-			throw InvalidAccessException("std::vector can only be bound immediately.");
+			throw InvalidAccessException("ODBC::Binder::bindImplContainerDate():std::vector can only be bound immediately.");
 
 		std::size_t length = val.size();
 
 		if (0 == length)
-			throw InvalidArgumentException("Empty vector not allowed.");
+			throw InvalidArgumentException("ODBC::Binder::bindImplContainerDate():Empty vector not allowed.");
 
 		setParamSetSize(length);
 
@@ -800,14 +800,14 @@ private:
 			(SQLUSMALLINT) pos + 1,
 			toODBCDirection(dir),
 			SQL_C_TYPE_DATE,
-			SQL_TYPE_DATE,
+			Utility::sqlDataType(SQL_C_TYPE_DATE),
 			colSize,
 			decDigits,
 			(SQLPOINTER) &(*_dateVecVec[pos])[0],
 			0,
 			&(*_vecLengthIndicator[pos])[0])))
 		{
-			throw StatementException(_rStmt, "SQLBindParameter(Date[])");
+			throw StatementException(_rStmt, "ODBC::Binder::bindImplContainerDate():SQLBindParameter(Date[])");
 		}
 	}
 
@@ -815,14 +815,14 @@ private:
 	void bindImplContainerTime(std::size_t pos, const C& val, Direction dir)
 	{
 		if (isOutBound(dir) || !isInBound(dir))
-			throw NotImplementedException("Time container parameter type can only be inbound.");
+			throw NotImplementedException("ODBC::Binder::bindImplContainerTime():Time container parameter type can only be inbound.");
 
 		if (PB_IMMEDIATE != _paramBinding)
-			throw InvalidAccessException("Containers can only be bound immediately.");
+			throw InvalidAccessException("ODBC::Binder::bindImplContainerTime():Containers can only be bound immediately.");
 
 		std::size_t length = val.size();
 		if (0 == length)
-			throw InvalidArgumentException("Empty container not allowed.");
+			throw InvalidArgumentException("ODBC::Binder::bindImplContainerTime():Empty container not allowed.");
 
 		setParamSetSize(val.size());
 
@@ -848,14 +848,14 @@ private:
 			(SQLUSMALLINT) pos + 1,
 			toODBCDirection(dir),
 			SQL_C_TYPE_TIME,
-			SQL_TYPE_TIME,
+			Utility::sqlDataType(SQL_C_TYPE_TIME),
 			colSize,
 			decDigits,
 			(SQLPOINTER) &(*_timeVecVec[pos])[0],
 			0,
 			&(*_vecLengthIndicator[pos])[0])))
 		{
-			throw StatementException(_rStmt, "SQLBindParameter(Time[])");
+			throw StatementException(_rStmt, "ODBC::Binder::bindImplContainerTime():SQLBindParameter(Time[])");
 		}
 	}
 
@@ -863,15 +863,15 @@ private:
 	void bindImplContainerDateTime(std::size_t pos, const C& val, Direction dir)
 	{
 		if (isOutBound(dir) || !isInBound(dir))
-			throw NotImplementedException("DateTime container parameter type can only be inbound.");
+			throw NotImplementedException("ODBC::Binder::bindImplContainerDateTime():DateTime container parameter type can only be inbound.");
 
 		if (PB_IMMEDIATE != _paramBinding)
-			throw InvalidAccessException("Containers can only be bound immediately.");
+			throw InvalidAccessException("ODBC::Binder::bindImplContainerDateTime():Containers can only be bound immediately.");
 
 		std::size_t length = val.size();
 
 		if (0 == length)
-			throw InvalidArgumentException("Empty Containers not allowed.");
+			throw InvalidArgumentException("ODBC::Binder::bindImplContainerDateTime():Empty Containers not allowed.");
 
 		setParamSetSize(length);
 
@@ -897,14 +897,14 @@ private:
 			(SQLUSMALLINT) pos + 1,
 			toODBCDirection(dir),
 			SQL_C_TYPE_TIMESTAMP,
-			SQL_TYPE_TIMESTAMP,
+			Utility::sqlDataType(SQL_C_TYPE_TIMESTAMP),
 			colSize,
 			decDigits,
 			(SQLPOINTER) &(*_dateTimeVecVec[pos])[0],
 			0,
 			&(*_vecLengthIndicator[pos])[0])))
 		{
-			throw StatementException(_rStmt, "SQLBindParameter(Time[])");
+			throw StatementException(_rStmt, "ODBC::Binder::bindImplContainerDateTime():SQLBindParameter(Time[])");
 		}
 	}
 
@@ -912,15 +912,15 @@ private:
 	void bindImplNullContainer(std::size_t pos, const C& val, Direction dir)
 	{
 		if (isOutBound(dir) || !isInBound(dir))
-			throw NotImplementedException("Null container parameter type can only be inbound.");
+			throw NotImplementedException("ODBC::Binder::bindImplNullContainer():Null container parameter type can only be inbound.");
 
 		if (PB_IMMEDIATE != _paramBinding)
-			throw InvalidAccessException("Container can only be bound immediately.");
+			throw InvalidAccessException("ODBC::Binder::bindImplNullContainer():Container can only be bound immediately.");
 
 		std::size_t length = val.size();
 
 		if (0 == length)
-			throw InvalidArgumentException("Empty container not allowed.");
+			throw InvalidArgumentException("ODBC::Binder::bindImplNullContainer():Empty container not allowed.");
 
 		setParamSetSize(length);
 
@@ -945,7 +945,7 @@ private:
 			0,
 			&(*_vecLengthIndicator[pos])[0])))
 		{
-			throw StatementException(_rStmt, "SQLBindParameter()");
+			throw StatementException(_rStmt, "ODBC::Binder::bindImplNullContainer():SQLBindParameter()");
 		}
 	}
 
@@ -973,6 +973,9 @@ private:
 		/// size should be set to some default value prior to calling this
 		/// function in order to avoid undefined size value.
 
+	std::size_t getParamSizeDirect(std::size_t pos, SQLINTEGER& size);
+		/// A "last ditch" attempt" to obtain parameter size directly from the driver.
+
 	void freeMemory();
 		/// Frees all dynamically allocated memory resources.
 

+ 49 - 6
Data/ODBC/include/Poco/Data/ODBC/ConnectionHandle.h

@@ -36,18 +36,40 @@ class ODBC_API ConnectionHandle
 /// ODBC connection handle class
 {
 public:
-	ConnectionHandle(EnvironmentHandle* pEnvironment = 0);
+	ConnectionHandle(const std::string& connectString = "", SQLULEN timeout = 5);
 		/// Creates the ConnectionHandle.
 
 	~ConnectionHandle();
 		/// Creates the ConnectionHandle.
 
-	operator const SQLHDBC& () const;
-		/// Const conversion operator into reference to native type.
+	bool connect(const std::string& connectString = "", SQLULEN timeout = 5);
+		/// Connects the handle to the database.
+
+	bool disconnect();
+		/// Disconnects the handle from database.
+
+	bool isConnected() const;
+		/// Returns true if connected.
+
+	void setTimeout(SQLULEN timeout);
+		/// Sets the connection timeout in seconds.
+
+	int getTimeout() const;
+		/// Returns the connection timeout in seconds.
 
 	const SQLHDBC& handle() const;
 		/// Returns const reference to handle;
 
+	const SQLHDBC* pHandle() const;
+		/// Returns const pointer to handle;
+
+	operator const SQLHDBC& () const;
+		/// Const conversion operator into reference to native type.
+
+	operator bool();
+		/// Returns true if handles are not null.
+		/// True value is not a guarantee that the connection is valid.
+
 private:
 	operator SQLHDBC& ();
 		/// Conversion operator into reference to native type.
@@ -55,17 +77,26 @@ private:
 	SQLHDBC& handle();
 		/// Returns reference to handle;
 
+	void alloc();
+		/// Allocates the connection handle.
+
+	void free();
+		/// Frees the connection handle.
+
 	ConnectionHandle(const ConnectionHandle&);
 	const ConnectionHandle& operator=(const ConnectionHandle&);
 
-	const EnvironmentHandle* _pEnvironment;
-	SQLHDBC                  _hdbc;
-	bool                     _ownsEnvironment;
+	const EnvironmentHandle* _pEnvironment = nullptr;
+	SQLHDBC                  _hdbc = SQL_NULL_HDBC;
+	std::string              _connectString;
 
 	friend class Poco::Data::ODBC::SessionImpl;
 };
 
 
+using Connection = ConnectionHandle;
+
+
 //
 // inlines
 //
@@ -75,12 +106,24 @@ inline ConnectionHandle::operator const SQLHDBC& () const
 }
 
 
+inline ConnectionHandle::operator bool()
+{
+	return _pEnvironment != nullptr && _hdbc != SQL_NULL_HDBC;
+}
+
+
 inline const SQLHDBC& ConnectionHandle::handle() const
 {
 	return _hdbc;
 }
 
 
+inline const SQLHDBC* ConnectionHandle::pHandle() const
+{
+	return &_hdbc;
+}
+
+
 inline ConnectionHandle::operator SQLHDBC& ()
 {
 	return handle();

+ 2 - 0
Data/ODBC/include/Poco/Data/ODBC/Diagnostics.h

@@ -138,6 +138,8 @@ public:
 
 	const Diagnostics& diagnostics()
 	{
+		if (SQL_NULL_HANDLE == _handle) return *this;
+
 		DiagnosticFields df;
 		SQLSMALLINT count = 1;
 		SQLSMALLINT messageLength = 0;

+ 0 - 1
Data/ODBC/include/Poco/Data/ODBC/EnvironmentHandle.h

@@ -57,7 +57,6 @@ private:
 	const EnvironmentHandle& operator=(const EnvironmentHandle&);
 
 	SQLHENV _henv;
-	bool    _isOwner;
 };
 
 

+ 3 - 1
Data/ODBC/include/Poco/Data/ODBC/Extractor.h

@@ -410,6 +410,7 @@ private:
 
 		CharType** pc = AnyCast<CharType*>(&(_pPreparator->at(pos)));
 		poco_assert_dbg (pc);
+		poco_assert_dbg(*pc);
 		poco_assert_dbg (_pPreparator->bulkSize() == values.size());
 		std::size_t colWidth = columnSize(pos);
 		ItType it = values.begin();
@@ -441,6 +442,7 @@ private:
 
 		CharType** pc = AnyCast<CharType*>(&(_pPreparator->at(pos)));
 		poco_assert_dbg (pc);
+		poco_assert_dbg(*pc);
 		poco_assert_dbg (_pPreparator->bulkSize() == values.size());
 		std::size_t colWidth = _pPreparator->maxDataSize(pos);
 		ItType it = values.begin();
@@ -480,7 +482,7 @@ private:
 			&_lengths[pos]);  //length indicator
 
 		if (Utility::isError(rc))
-			throw StatementException(_rStmt, "SQLGetData()");
+			throw StatementException(_rStmt, "ODBC::Extractor::extractManualImpl():SQLGetData()");
 
 		if (isNullLengthIndicator(_lengths[pos]))
 			return false;

+ 23 - 11
Data/ODBC/include/Poco/Data/ODBC/ODBCStatementImpl.h

@@ -94,6 +94,9 @@ protected:
 	std::string nativeSQL();
 		/// Returns the SQL string as modified by the driver.
 
+	void printErrors(std::ostream& os) const;
+		/// Print errors, if any.
+
 private:
 	typedef Poco::Data::AbstractBindingVec    Bindings;
 	typedef Poco::SharedPtr<Binder>           BinderPtr;
@@ -143,17 +146,26 @@ private:
 	void fillColumns();
 	void checkError(SQLRETURN rc, const std::string& msg="");
 
-	const SQLHDBC&        _rConnection;
-	const StatementHandle _stmt;
-	PreparatorVec         _preparations;
-	BinderPtr             _pBinder;
-	ExtractorVec          _extractors;
-	bool                  _stepCalled;
-	int                   _nextResponse;
-	ColumnPtrVecVec       _columnPtrs;
-	bool                  _prepared;
-	mutable std::size_t   _affectedRowCount;
-	bool                  _canCompile;
+	struct ERROR_INFO
+	{
+		SQLCHAR state[8];
+		SQLINTEGER native;
+		SQLCHAR text[256];
+	};
+	void addErrors();
+
+	const SQLHDBC&          _rConnection;
+	const StatementHandle   _stmt;
+	PreparatorVec           _preparations;
+	BinderPtr               _pBinder;
+	ExtractorVec            _extractors;
+	bool                    _stepCalled;
+	int                     _nextResponse;
+	ColumnPtrVecVec         _columnPtrs;
+	bool                    _prepared;
+	mutable std::size_t     _affectedRowCount;
+	bool                    _canCompile;
+	std::vector<ERROR_INFO> _errorInfo;
 };
 
 

+ 4 - 4
Data/ODBC/include/Poco/Data/ODBC/Preparator.h

@@ -583,7 +583,7 @@ private:
 			(SQLINTEGER) dataSize,
 			&_lengths[pos])))
 		{
-			throw StatementException(_rStmt, "SQLBindCol()");
+			throw StatementException(_rStmt, "ODBC::Preparator::prepareFixedSize():SQLBindCol()");
 		}
 	}
 
@@ -612,7 +612,7 @@ private:
 			(SQLINTEGER) dataSize,
 			&_lenLengths[pos][0])))
 		{
-			throw StatementException(_rStmt, "SQLBindCol()");
+			throw StatementException(_rStmt, "ODBC::Preparator::prepareFixedSize():SQLBindCol()");
 		}
 	}
 
@@ -637,7 +637,7 @@ private:
 			(SQLINTEGER) size*sizeof(T),
 			&_lengths[pos])))
 		{
-			throw StatementException(_rStmt, "SQLBindCol()");
+			throw StatementException(_rStmt, "ODBC::Preparator::prepareVariableLen():SQLBindCol()");
 		}
 	}
 
@@ -664,7 +664,7 @@ private:
 			(SQLINTEGER) size,
 			&_lenLengths[pos][0])))
 		{
-			throw StatementException(_rStmt, "SQLBindCol()");
+			throw StatementException(_rStmt, "ODBC::Preparator::prepareCharArray():SQLBindCol()");
 		}
 	}
 

+ 27 - 11
Data/ODBC/include/Poco/Data/ODBC/SessionImpl.h

@@ -52,6 +52,13 @@ public:
 		ODBC_TXN_CAPABILITY_TRUE = 1
 	};
 
+	enum CursorUse
+	{
+		ODBC_CURSOR_USE_ALWAYS = Poco::Data::SessionImpl::CURSOR_USE_ALWAYS,
+		ODBC_CURSOR_USE_IF_NEEDED = Poco::Data::SessionImpl::CURSOR_USE_IF_NEEDED,
+		ODBC_CURSOR_USE_NEVER = Poco::Data::SessionImpl::CURSOR_USE_NEVER
+	};
+
 	SessionImpl(const std::string& connect,
 		std::size_t loginTimeout,
 		std::size_t maxFieldSize = ODBC_MAX_FIELD_SIZE,
@@ -159,6 +166,15 @@ public:
 		/// Returns the timeout (in seconds) for queries,
 		/// or -1 if no timeout has been set.
 
+	void setCursorUse(const std::string&, const Poco::Any& value);
+		/// Sets the use of cursors:
+		///   - SQL_CUR_USE_ODBC - always
+		///   - SQL_CUR_USE_IF_NEEDED - if needed
+		///   - SQL_CUR_USE_DRIVER - never
+
+	Poco::Any getCursorUse(const std::string&) const;
+		/// Returns the use of cursors.
+
 	int queryTimeout() const;
 		/// Returns the timeout (in seconds) for queries,
 		/// or -1 if no timeout has been set.
@@ -195,17 +211,17 @@ private:
 		/// Sets the transaction isolation level.
 		/// Called internally from getTransactionIsolation()
 
-	std::string            _connector;
-	mutable ConnectionHandle _db;
-	Poco::Any              _maxFieldSize;
-	bool                   _autoBind;
-	bool                   _autoExtract;
-	TypeInfo               _dataTypes;
-	mutable char           _canTransact;
-	bool                   _inTransaction;
-	int                    _queryTimeout;
-	std::string            _dbEncoding;
-	Poco::FastMutex        _mutex;
+	std::string                   _connector;
+	mutable ConnectionHandle      _db;
+	Poco::Any                     _maxFieldSize;
+	bool                          _autoBind;
+	bool                          _autoExtract;
+	TypeInfo                      _dataTypes;
+	mutable TransactionCapability _canTransact;
+	std::atomic<bool>             _inTransaction;
+	int                           _queryTimeout;
+	std::string                   _dbEncoding;
+	Poco::FastMutex               _mutex;
 };
 
 

+ 2 - 2
Data/ODBC/include/Poco/Data/ODBC/TypeInfo.h

@@ -82,7 +82,7 @@ public:
 	int sqlDataType(int cDataType) const;
 		/// Returns SQL data type corresponding to supplied C data type.
 
-	void fillTypeInfo(SQLHDBC pHDBC);
+	void fillTypeInfo(const SQLHDBC* pHDBC);
 		/// Fills the data type info structure for the database.
 
 	DynamicAny getInfo(SQLSMALLINT type, const std::string& param) const;
@@ -107,7 +107,7 @@ private:
 	DataTypeMap _cDataTypes;
 	DataTypeMap _sqlDataTypes;
 	TypeInfoVec _typeInfo;
-	SQLHDBC*    _pHDBC;
+	const SQLHDBC*    _pHDBC;
 };
 
 

+ 52 - 46
Data/ODBC/src/Binder.cpp

@@ -85,7 +85,7 @@ void Binder::freeMemory()
 
 	UUIDMap::iterator itUUID = _uuids.begin();
 	UUIDMap::iterator itUUIDEnd = _uuids.end();
-	for(; itUUID != itUUIDEnd; ++itUUID) std::free(itUUID->first);
+	for(; itUUID != itUUIDEnd; ++itUUID) delete [] itUUID->first;
 
 	BoolPtrVec::iterator itBool = _boolPtrs.begin();
 	BoolPtrVec::iterator endBool = _boolPtrs.end();
@@ -152,7 +152,7 @@ void Binder::bind(std::size_t pos, const std::string& val, Direction dir)
 		}
 	}
 	else
-		throw InvalidArgumentException("Parameter must be [in] OR [out] bound.");
+		throw InvalidArgumentException("ODBC::Binder::bind(string):Parameter must be [in] OR [out] bound.");
 
 	SQLLEN* pLenIn = new SQLLEN(SQL_NTS);
 
@@ -165,14 +165,14 @@ void Binder::bind(std::size_t pos, const std::string& val, Direction dir)
 		(SQLUSMALLINT) pos + 1,
 		toODBCDirection(dir),
 		SQL_C_CHAR,
-		Connector::stringBoundToLongVarChar() ? SQL_LONGVARCHAR : SQL_VARCHAR,
+		Utility::sqlDataType(SQL_C_CHAR),
 		(SQLUINTEGER) colSize,
 		0,
 		pVal,
 		(SQLINTEGER) size,
 		_lengthIndicator.back())))
 	{
-		throw StatementException(_rStmt, "SQLBindParameter(std::string)");
+		throw StatementException(_rStmt, "ODBC::Binder::bind(string):SQLBindParameter(std::string)");
 	}
 }
 
@@ -201,7 +201,7 @@ void Binder::bind(std::size_t pos, const UTF16String& val, Direction dir)
 		_inParams.insert(ParamMap::value_type(pVal, size));
 	}
 	else
-		throw InvalidArgumentException("Parameter must be [in] OR [out] bound.");
+		throw InvalidArgumentException("ODBC::Binder::bind():Parameter must be [in] OR [out] bound.");
 
 	SQLLEN* pLenIn = new SQLLEN(SQL_NTS);
 
@@ -216,14 +216,14 @@ void Binder::bind(std::size_t pos, const UTF16String& val, Direction dir)
 		(SQLUSMALLINT)pos + 1,
 		toODBCDirection(dir),
 		SQL_C_WCHAR,
-		SQL_WLONGVARCHAR,
+		Utility::sqlDataType(SQL_C_WCHAR),
 		(SQLUINTEGER)colSize,
 		0,
 		pVal,
 		(SQLINTEGER)size,
 		_lengthIndicator.back())))
 	{
-		throw StatementException(_rStmt, "SQLBindParameter(std::string)");
+		throw StatementException(_rStmt, "ODBC::Binder::bind(UTF16String):SQLBindParameter(std::string)");
 	}
 }
 
@@ -249,14 +249,14 @@ void Binder::bind(std::size_t pos, const Date& val, Direction dir)
 		(SQLUSMALLINT) pos + 1,
 		toODBCDirection(dir),
 		SQL_C_TYPE_DATE,
-		SQL_TYPE_DATE,
+		Utility::sqlDataType(SQL_C_TYPE_DATE),
 		colSize,
 		decDigits,
 		(SQLPOINTER) pDS,
 		0,
 		_lengthIndicator.back())))
 	{
-		throw StatementException(_rStmt, "SQLBindParameter(Date)");
+		throw StatementException(_rStmt, "ODBC::Binder::bind(Date):SQLBindParameter(Date)");
 	}
 }
 
@@ -282,14 +282,14 @@ void Binder::bind(std::size_t pos, const Time& val, Direction dir)
 		(SQLUSMALLINT) pos + 1,
 		toODBCDirection(dir),
 		SQL_C_TYPE_TIME,
-		SQL_TYPE_TIME,
+		Utility::sqlDataType(SQL_C_TYPE_TIME),
 		colSize,
 		decDigits,
 		(SQLPOINTER) pTS,
 		0,
 		_lengthIndicator.back())))
 	{
-		throw StatementException(_rStmt, "SQLBindParameter(Time)");
+		throw StatementException(_rStmt, "ODBC::Binder::bind(Time):SQLBindParameter(Time)");
 	}
 }
 
@@ -315,14 +315,14 @@ void Binder::bind(std::size_t pos, const Poco::DateTime& val, Direction dir)
 		(SQLUSMALLINT) pos + 1,
 		toODBCDirection(dir),
 		SQL_C_TYPE_TIMESTAMP,
-		SQL_TYPE_TIMESTAMP,
+		Utility::sqlDataType(SQL_C_TYPE_TIMESTAMP),
 		colSize,
 		decDigits,
 		(SQLPOINTER) pTS,
 		0,
 		_lengthIndicator.back())))
 	{
-		throw StatementException(_rStmt, "SQLBindParameter(DateTime)");
+		throw StatementException(_rStmt, "ODBC::Binder::bind(DateTime):SQLBindParameter(DateTime)");
 	}
 }
 
@@ -386,7 +386,7 @@ void Binder::bind(std::size_t pos, const NullData& val, Direction dir)
 		0,
 		_lengthIndicator.back())))
 	{
-		throw StatementException(_rStmt, "SQLBindParameter()");
+		throw StatementException(_rStmt, "ODBC::Binder::bind(NullData):SQLBindParameter()");
 	}
 }
 
@@ -510,25 +510,28 @@ void Binder::getColSizeAndPrecision(std::size_t pos,
 	SQLSMALLINT& decDigits,
 	std::size_t actualSize)
 {
+	colSize = 0;
+	decDigits = 0;
+
 	// Not all drivers are equally willing to cooperate in this matter.
 	// Hence the funky flow control.
-	DynamicAny tmp;
-	bool found(false);
 	if (_pTypeInfo)
 	{
-		found = _pTypeInfo->tryGetInfo(cDataType, "COLUMN_SIZE", tmp);
-		if (found) colSize = tmp;
-		if (found && actualSize > colSize)
+		DynamicAny tmp;
+		bool foundSize(false);
+		bool foundPrec(false);
+		foundSize = _pTypeInfo->tryGetInfo(cDataType, "COLUMN_SIZE", tmp);
+		if (foundSize) colSize = tmp;
+		if (actualSize > colSize)
 		{
 			throw LengthExceededException(Poco::format("Error binding column %z size=%z, max size=%ld)",
 					pos, actualSize, static_cast<long>(colSize)));
 		}
-		found = _pTypeInfo->tryGetInfo(cDataType, "MINIMUM_SCALE", tmp);
-		if (found)
-		{
-			decDigits = tmp;
+		foundPrec = _pTypeInfo->tryGetInfo(cDataType, "MAXIMUM_SCALE", tmp);
+		if (foundPrec) decDigits = tmp;
+
+		if (foundSize && foundPrec)
 			return;
-		}
 	}
 
 	try
@@ -560,14 +563,30 @@ void Binder::getColSizeAndPrecision(std::size_t pos,
 				pos, actualSize, static_cast<long>(colSize)));
 	}
 
-	// no success, set to zero and hope for the best
-	// (most drivers do not require these most of the times anyway)
-	colSize = 0;
-	decDigits = 0;
 	return;
 }
 
 
+std::size_t Binder::getParamSizeDirect(std::size_t pos, SQLINTEGER& size)
+{
+//On Linux, PostgreSQL driver segfaults on SQLGetDescField, so this is disabled for now
+#ifdef POCO_OS_FAMILY_WINDOWS
+	size = DEFAULT_PARAM_SIZE;
+	SQLHDESC hIPD = 0;
+	if (!Utility::isError(SQLGetStmtAttr(_rStmt, SQL_ATTR_IMP_PARAM_DESC, &hIPD, SQL_IS_POINTER, 0)))
+	{
+		SQLULEN sz = 0;
+		if (!Utility::isError(SQLGetDescField(hIPD, (SQLSMALLINT)pos + 1, SQL_DESC_LENGTH, &sz, SQL_IS_UINTEGER, 0)) &&
+			sz > 0)
+		{
+			size = (SQLINTEGER)sz;
+		}
+	}
+#endif
+	return static_cast<std::size_t>(size);
+}
+
+
 void Binder::getColumnOrParameterSize(std::size_t pos, SQLINTEGER& size)
 {
 	std::size_t colSize = 0;
@@ -585,23 +604,10 @@ void Binder::getColumnOrParameterSize(std::size_t pos, SQLINTEGER& size)
 		Parameter p(_rStmt, pos);
 		paramSize = p.columnSize();
 	}
-	catch (StatementException&)
-	{
-		size = DEFAULT_PARAM_SIZE;
-//On Linux, PostgreSQL driver segfaults on SQLGetDescField, so this is disabled for now
-#ifdef POCO_OS_FAMILY_WINDOWS
-		SQLHDESC hIPD = 0;
-		if (!Utility::isError(SQLGetStmtAttr(_rStmt, SQL_ATTR_IMP_PARAM_DESC, &hIPD, SQL_IS_POINTER, 0)))
-		{
-			SQLUINTEGER sz = 0;
-			if (!Utility::isError(SQLGetDescField(hIPD, (SQLSMALLINT) pos + 1, SQL_DESC_LENGTH, &sz, SQL_IS_UINTEGER, 0)) &&
-				sz > 0)
-			{
-				size = sz;
-			}
-		}
-#endif
-	}
+	catch (StatementException&) {}
+
+	if (colSize == 0 && paramSize == 0)
+		paramSize = getParamSizeDirect(pos, size);
 
 	if (colSize > 0 && paramSize > 0)
 		size = colSize < paramSize ? static_cast<SQLINTEGER>(colSize) : static_cast<SQLINTEGER>(paramSize);
@@ -620,7 +626,7 @@ void Binder::setParamSetSize(std::size_t length)
 	{
 		if (Utility::isError(Poco::Data::ODBC::SQLSetStmtAttr(_rStmt, SQL_ATTR_PARAM_BIND_TYPE, SQL_PARAM_BIND_BY_COLUMN, SQL_IS_UINTEGER)) ||
 			Utility::isError(Poco::Data::ODBC::SQLSetStmtAttr(_rStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) length, SQL_IS_UINTEGER)))
-				throw StatementException(_rStmt, "SQLSetStmtAttr()");
+				throw StatementException(_rStmt, "ODBC::Binder::setParamSetSize():SQLSetStmtAttr()");
 
 		_paramSetSize = static_cast<SQLINTEGER>(length);
 	}

+ 117 - 16
Data/ODBC/src/ConnectionHandle.cpp

@@ -24,17 +24,12 @@ namespace Data {
 namespace ODBC {
 
 
-ConnectionHandle::ConnectionHandle(EnvironmentHandle* pEnvironment):
-	_pEnvironment(pEnvironment ? pEnvironment : new EnvironmentHandle),
+ConnectionHandle::ConnectionHandle(const std::string& connectString, SQLULEN timeout): _pEnvironment(nullptr),
 	_hdbc(SQL_NULL_HDBC),
-	_ownsEnvironment(pEnvironment ? false : true)
+	_connectString(connectString)
 {
-	if (Utility::isError(SQLAllocHandle(SQL_HANDLE_DBC,
-		_pEnvironment->handle(),
-		&_hdbc)))
-	{
-		throw ODBCException("Could not allocate connection handle.");
-	}
+	alloc();
+	setTimeout(timeout);
 }
 
 
@@ -42,13 +37,7 @@ ConnectionHandle::~ConnectionHandle()
 {
 	try
 	{
-		SQLDisconnect(_hdbc);
-		SQLRETURN rc = SQLFreeHandle(SQL_HANDLE_DBC, _hdbc);
-		if (_ownsEnvironment) delete _pEnvironment;
-#if defined(_DEBUG)
-		if (Utility::isError(rc))
-			Debugger::enter(Poco::Error::getMessage(Poco::Error::last()), __FILE__, __LINE__);
-#endif
+		disconnect();
 	}
 	catch (...)
 	{
@@ -57,4 +46,116 @@ ConnectionHandle::~ConnectionHandle()
 }
 
 
+void ConnectionHandle::alloc()
+{
+	if (_pEnvironment || _hdbc) free();
+	_pEnvironment = new EnvironmentHandle;
+	if (Utility::isError(SQLAllocHandle(SQL_HANDLE_DBC, _pEnvironment->handle(), &_hdbc)))
+	{
+		delete _pEnvironment;
+		_pEnvironment = nullptr;
+		_hdbc = SQL_NULL_HDBC;
+		throw ODBCException("ODBC: Could not allocate connection handle.");
+	}
+}
+
+
+void ConnectionHandle::free()
+{
+	if (_hdbc != SQL_NULL_HDBC)
+	{
+		SQLFreeHandle(SQL_HANDLE_DBC, _hdbc);
+		_hdbc = SQL_NULL_HDBC;
+	}
+
+	if (_pEnvironment)
+	{
+		delete _pEnvironment;
+		_pEnvironment = 0;
+	}
+}
+
+
+bool ConnectionHandle::connect(const std::string& connectString, SQLULEN timeout)
+{
+	if (isConnected())
+		throw Poco::InvalidAccessException("ODBC: connection already established.");
+
+	if (connectString.empty())
+		throw Poco::InvalidArgumentException("ODBC: connection string is empty.");
+
+	if (connectString != _connectString)
+		_connectString = connectString;
+
+	SQLCHAR connectOutput[512] = {0};
+	SQLSMALLINT result;
+
+	if (!_pEnvironment) alloc();
+	if (Utility::isError(Poco::Data::ODBC::SQLDriverConnect(_hdbc
+		, NULL
+		,(SQLCHAR*) _connectString.c_str()
+		,(SQLSMALLINT) SQL_NTS
+		, connectOutput
+		, sizeof(connectOutput)
+		, &result
+		, SQL_DRIVER_NOPROMPT)))
+	{
+		disconnect();
+		ConnectionError err(_hdbc);
+		throw ConnectionFailedException(err.toString());
+	}
+
+	return _hdbc != SQL_NULL_HDBC;;
+}
+
+
+bool ConnectionHandle::disconnect()
+{
+	SQLRETURN rc = 0;
+	if (isConnected())
+		rc = SQLDisconnect(_hdbc);
+
+	free();
+	return !Utility::isError(rc);
+}
+
+
+void ConnectionHandle::setTimeout(SQLULEN timeout)
+{
+	if (Utility::isError(SQLSetConnectAttr(_hdbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) timeout, 0)))
+	{
+		ConnectionError e(_hdbc);
+		throw ConnectionFailedException(e.toString());
+	}
+}
+
+
+int ConnectionHandle::getTimeout() const
+{
+	SQLULEN timeout = 0;
+	if (Utility::isError(SQLGetConnectAttr(_hdbc, SQL_ATTR_LOGIN_TIMEOUT, &timeout, 0, 0)))
+	{
+		ConnectionError e(_hdbc);
+		throw ConnectionFailedException(e.toString());
+	}
+	return static_cast<int>(timeout);
+}
+
+
+bool ConnectionHandle::isConnected() const
+{
+	if (!*this) return false;
+
+	SQLULEN value = 0;
+
+	if (Utility::isError(Poco::Data::ODBC::SQLGetConnectAttr(_hdbc,
+		SQL_ATTR_CONNECTION_DEAD,
+		&value,
+		0,
+		0))) return false;
+
+	return (SQL_CD_FALSE == value);
+}
+
+
 } } } // namespace Poco::Data::ODBC

+ 15 - 10
Data/ODBC/src/EnvironmentHandle.cpp

@@ -26,15 +26,15 @@ namespace ODBC {
 
 EnvironmentHandle::EnvironmentHandle(): _henv(SQL_NULL_HENV)
 {
-	if (Utility::isError(SQLAllocHandle(SQL_HANDLE_ENV,
-			SQL_NULL_HANDLE,
-			&_henv)) ||
-		Utility::isError(SQLSetEnvAttr(_henv,
-			SQL_ATTR_ODBC_VERSION,
-			(SQLPOINTER) SQL_OV_ODBC3,
-			0)))
+	SQLRETURN rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &_henv);
+	if (Utility::isError(rc))
+		throw ODBCException("EnvironmentHandle: Could not initialize ODBC environment.");
+
+	rc = SQLSetEnvAttr(_henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, 0);
+	if (Utility::isError(rc))
 	{
-		throw ODBCException("Could not initialize environment.");
+		EnvironmentError err(_henv);
+		throw ODBCException(err.toString());
 	}
 }
 
@@ -43,10 +43,15 @@ EnvironmentHandle::~EnvironmentHandle()
 {
 	try
 	{
-		SQLRETURN rc = SQLFreeHandle(SQL_HANDLE_ENV, _henv);
 #if defined(_DEBUG)
+		SQLRETURN rc = SQLFreeHandle(SQL_HANDLE_ENV, _henv);
 		if (Utility::isError(rc))
-			Debugger::enter(Poco::Error::getMessage(Poco::Error::last()), __FILE__, __LINE__);
+		{
+			EnvironmentError err(_henv);
+			Debugger::enter(err.toString(), __FILE__, __LINE__);
+		}
+#else
+		SQLFreeHandle(SQL_HANDLE_ENV, _henv);
 #endif
 	}
 	catch (...)

+ 10 - 9
Data/ODBC/src/Extractor.cpp

@@ -19,6 +19,7 @@
 #include "Poco/Data/ODBC/ODBCException.h"
 #include "Poco/Data/LOB.h"
 #include "Poco/Buffer.h"
+#include "Poco/Exception.h"
 #include <typeinfo>
 
 
@@ -300,10 +301,10 @@ bool Extractor::extractManualImpl<std::string>(std::size_t pos, std::string& val
 			&len); //length indicator
 
 		if (SQL_NO_DATA != rc && Utility::isError(rc))
-			throw StatementException(_rStmt, "SQLGetData()");
+			throw StatementException(_rStmt, "ODBC::Extractor::extractManualImpl(string):SQLGetData()");
 
 		if (SQL_NO_TOTAL == len)//unknown length, throw
-			throw UnknownDataLengthException("Could not determine returned data length.");
+			throw UnknownDataLengthException("ODBC::Extractor::extractManualImpl(string):Could not determine returned data length.");
 
 		if (isNullLengthIndicator(len))
 		{
@@ -355,10 +356,10 @@ bool Extractor::extractManualImpl<UTF16String>(std::size_t pos, UTF16String& val
 			&len); //length indicator
 
 		if (SQL_NO_DATA != rc && Utility::isError(rc))
-			throw StatementException(_rStmt, "SQLGetData()");
+			throw StatementException(_rStmt, "ODBC::Extractor::extractManualImpl(UTF16String):SQLGetData()");
 
 		if (SQL_NO_TOTAL == len)//unknown length, throw
-			throw UnknownDataLengthException("Could not determine returned data length.");
+			throw UnknownDataLengthException("ODBC::Extractor::extractManualImpl(UTF16String):Could not determine returned data length.");
 
 		if (isNullLengthIndicator(len))
 		{
@@ -414,10 +415,10 @@ bool Extractor::extractManualImpl<Poco::Data::CLOB>(std::size_t pos,
 		_lengths[pos] += len;
 
 		if (SQL_NO_DATA != rc && Utility::isError(rc))
-			throw StatementException(_rStmt, "SQLGetData()");
+			throw StatementException(_rStmt, "ODBC::Extractor::extractManualImpl(CLOB):SQLGetData()");
 
 		if (SQL_NO_TOTAL == len)//unknown length, throw
-			throw UnknownDataLengthException("Could not determine returned data length.");
+			throw UnknownDataLengthException("ODBC::Extractor::extractManualImpl(CLOB):Could not determine returned data length.");
 
 		if (isNullLengthIndicator(len))
 			return false;
@@ -454,7 +455,7 @@ bool Extractor::extractManualImpl<Poco::Data::Date>(std::size_t pos,
 		&_lengths[pos]); //length indicator
 
 	if (Utility::isError(rc))
-		throw StatementException(_rStmt, "SQLGetData()");
+		throw StatementException(_rStmt, "ODBC::Extractor::extractManualImpl(Date):SQLGetData()");
 
 	if (isNullLengthIndicator(_lengths[pos]))
 		return false;
@@ -481,7 +482,7 @@ bool Extractor::extractManualImpl<Poco::Data::Time>(std::size_t pos,
 		&_lengths[pos]); //length indicator
 
 	if (Utility::isError(rc))
-		throw StatementException(_rStmt, "SQLGetData()");
+		throw StatementException(_rStmt, "ODBC::Extractor::extractManualImpl(Time):SQLGetData()");
 
 	if (isNullLengthIndicator(_lengths[pos]))
 		return false;
@@ -508,7 +509,7 @@ bool Extractor::extractManualImpl<Poco::DateTime>(std::size_t pos,
 		&_lengths[pos]); //length indicator
 
 	if (Utility::isError(rc))
-		throw StatementException(_rStmt, "SQLGetData()");
+		throw StatementException(_rStmt, "ODBC::Extractor::extractManualImpl(DateTime):SQLGetData()");
 
 	if (isNullLengthIndicator(_lengths[pos]))
 		return false;

+ 3 - 3
Data/ODBC/src/ODBCMetaColumn.cpp

@@ -53,7 +53,7 @@ void ODBCMetaColumn::getDescription()
 		&_columnDesc.decimalDigits,
 		&_columnDesc.isNullable)))
 	{
-		throw StatementException(_rStmt);
+		throw StatementException(_rStmt, "ODBCMetaColumn::getDescription()");
 	}
 }
 
@@ -69,7 +69,7 @@ bool ODBCMetaColumn::isUnsigned() const
 		0,
 		&val)))
 	{
-		throw StatementException(_rStmt);
+		throw StatementException(_rStmt, "ODBCMetaColumn::isUnsigned()");
 	}
 	return (val == SQL_TRUE);
 }
@@ -87,7 +87,7 @@ void ODBCMetaColumn::init()
 			0,
 			&_dataLength)))
 	{
-		throw StatementException(_rStmt);
+		throw StatementException(_rStmt, "ODBCMetaColumn::init()");
 	}
 
 	setName(std::string((char*) _columnDesc.name));

+ 86 - 29
Data/ODBC/src/ODBCStatementImpl.cpp

@@ -47,13 +47,19 @@ ODBCStatementImpl::ODBCStatementImpl(SessionImpl& rSession):
 	_canCompile(true)
 {
 	int queryTimeout = rSession.queryTimeout();
+	int rc = 0;
 	if (queryTimeout >= 0)
 	{
 		SQLULEN uqt = static_cast<SQLULEN>(queryTimeout);
-		SQLSetStmtAttr(_stmt,
+		rc = SQLSetStmtAttr(_stmt,
 			SQL_ATTR_QUERY_TIMEOUT,
 			(SQLPOINTER) uqt,
 			0);
+		if (Utility::isError(rc))
+		{
+			throw ODBC::ConnectionException(_stmt,
+				Poco::format("SQLSetStmtAttr(SQL_ATTR_QUERY_TIMEOUT, %d)", queryTimeout));
+		}
 	}
 }
 
@@ -96,7 +102,15 @@ void ODBCStatementImpl::compileImpl()
 	{
 	}
 
-	std::size_t maxFieldSize = AnyCast<std::size_t>(session().getProperty("maxFieldSize"));
+	std::size_t maxFieldSize;
+	try
+	{
+		maxFieldSize = AnyCast<std::size_t>(session().getProperty("maxFieldSize"));
+	}
+	catch (Poco::BadCastException&)
+	{
+		maxFieldSize = AnyCast<int>(session().getProperty("maxFieldSize"));
+	}
 
 	_pBinder = new Binder(_stmt, maxFieldSize, bind, pDT, TextEncoding::find("UTF-8"),
 		TextEncoding::find(Poco::RefAnyCast<std::string>(session().getProperty("dbEncoding"))));
@@ -139,7 +153,15 @@ void ODBCStatementImpl::addPreparator()
 		Preparator::DataExtraction ext = session().getFeature("autoExtract") ?
 			Preparator::DE_BOUND : Preparator::DE_MANUAL;
 
-		std::size_t maxFieldSize = AnyCast<std::size_t>(session().getProperty("maxFieldSize"));
+		std::size_t maxFieldSize;
+		try
+		{
+			maxFieldSize = AnyCast<std::size_t>(session().getProperty("maxFieldSize"));
+		}
+		catch (Poco::BadCastException&)
+		{
+			maxFieldSize = AnyCast<int>(session().getProperty("maxFieldSize"));
+		}
 
 		_preparations.push_back(new Preparator(_stmt, statement, maxFieldSize, ext));
 	}
@@ -216,6 +238,38 @@ void ODBCStatementImpl::doBind()
 }
 
 
+void ODBCStatementImpl::addErrors()
+{
+	SQLSMALLINT i = 0;
+	SQLSMALLINT len;
+	SQLRETURN ret;
+	do
+	{
+		_errorInfo.push_back({});
+		ret = SQLGetDiagRec(SQL_HANDLE_STMT, _stmt, ++i,
+			_errorInfo.back().state, &_errorInfo.back().native, _errorInfo.back().text,
+			sizeof(_errorInfo.back().text), &len);
+		if (!SQL_SUCCEEDED(ret) && _errorInfo.size())
+			_errorInfo.pop_back();
+	} while( ret == SQL_SUCCESS );
+}
+
+
+void ODBCStatementImpl::printErrors(std::ostream& os) const
+{
+	if (_errorInfo.size())
+	{
+		os << "Errors\n==================";
+		for (const auto& e : _errorInfo)
+		{
+			os << "\nstate: " << e.state << "\nnative: "
+				<< e.native << "\ntext: " << e.text << '\n';
+		}
+		os << "==================\n";
+	}
+}
+
+
 void ODBCStatementImpl::bindImpl()
 {
 	doBind();
@@ -223,15 +277,16 @@ void ODBCStatementImpl::bindImpl()
 	SQLRETURN rc = SQLExecute(_stmt);
 
 	if (SQL_NEED_DATA == rc) putData();
-	else checkError(rc, "SQLExecute()");
+	else checkError(rc, "ODBCStatementImpl::bindImpl():SQLExecute()");
 
 	_pBinder->synchronize();
 }
 
+
 void ODBCStatementImpl::execDirectImpl(const std::string& query)
 {
 	SQLCHAR * statementText = (SQLCHAR*) query.c_str();
-	SQLINTEGER textLength = query.size();
+	SQLINTEGER textLength = static_cast<SQLINTEGER>(query.size());
 	SQLRETURN rc = SQLExecDirect(_stmt,statementText,textLength);
 
 	checkError(rc, "SQLExecute()");
@@ -251,13 +306,13 @@ void ODBCStatementImpl::putData()
 			dataSize = (SQLINTEGER) _pBinder->parameterSize(pParam);
 
 			if (Utility::isError(SQLPutData(_stmt, pParam, dataSize)))
-				throw StatementException(_stmt, "SQLPutData()");
+				throw StatementException(_stmt, "ODBCStatementImpl::putData():SQLPutData()");
 		}
 		else // if pParam is null pointer, do a dummy call
 		{
 			char dummy = 0;
 			if (Utility::isError(SQLPutData(_stmt, &dummy, 0)))
-				throw StatementException(_stmt, "SQLPutData()");
+				throw StatementException(_stmt, "ODBCStatementImpl::putData():SQLPutData()");
 		}
 	}
 
@@ -267,30 +322,12 @@ void ODBCStatementImpl::putData()
 
 void ODBCStatementImpl::clear()
 {
-	SQLRETURN rc = SQLCloseCursor(_stmt);
 	_stepCalled = false;
 	_affectedRowCount = 0;
-
+	_errorInfo.clear();
+	SQLRETURN rc = SQLFreeStmt(_stmt, SQL_CLOSE);
 	if (Utility::isError(rc))
-	{
-		StatementError err(_stmt);
-		bool ignoreError = false;
-
-		const StatementDiagnostics& diagnostics = err.diagnostics();
-		//ignore "Invalid cursor state" error
-		//(returned by 3.x drivers when cursor is not opened)
-		for (int i = 0; i < diagnostics.count(); ++i)
-		{
-			if ((ignoreError =
-				(INVALID_CURSOR_STATE == std::string(diagnostics.sqlState(i)))))
-			{
-				break;
-			}
-		}
-
-		if (!ignoreError)
-			throw StatementException(_stmt, "SQLCloseCursor()");
-	}
+		throw StatementException(_stmt, "ODBCStatementImpl::putData():SQLFreeStmt(SQL_CLOSE)");
 }
 
 
@@ -335,6 +372,25 @@ void ODBCStatementImpl::makeStep()
 {
 	_extractors[currentDataSet()]->reset();
 	_nextResponse = SQLFetch(_stmt);
+	// workaround for SQL Server drivers 17, 18, ...
+	// stored procedure calls produce additional data,
+	// causing SQLFetch error 24000 (invalid cursor state);
+	// when it happens, SQLMoreResults() is called to
+	// force SQL_NO_DATA response
+	if (Utility::isError(_nextResponse))
+	{
+		StatementError se(_stmt);
+		const StatementDiagnostics& sd = se.diagnostics();
+
+		for (int i = 0; i < sd.count(); ++i)
+		{
+			if (sd.sqlState(i) == INVALID_CURSOR_STATE)
+			{
+				_nextResponse = SQLMoreResults(_stmt);
+				break;
+			}
+		}
+	}
 	checkError(_nextResponse);
 	_stepCalled = true;
 }
@@ -363,7 +419,7 @@ std::size_t ODBCStatementImpl::next()
 	else
 	{
 		throw StatementException(_stmt,
-			std::string("Next row not available."));
+			"ODBCStatementImpl::next():Next row not available.");
 	}
 
 	return count;
@@ -416,6 +472,7 @@ void ODBCStatementImpl::checkError(SQLRETURN rc, const std::string& msg)
 
 		throw StatementException(_stmt, str);
 	}
+	else if (SQL_SUCCESS_WITH_INFO == rc) addErrors();
 }
 
 

+ 1 - 1
Data/ODBC/src/Parameter.cpp

@@ -45,7 +45,7 @@ void Parameter::init()
 		&_decimalDigits,
 		&_isNullable)))
 	{
-		throw StatementException(_rStmt);
+		throw StatementException(_rStmt, "ODBC::Parameter::init():SQLDescribeParam()");
 	}
 }
 

+ 4 - 3
Data/ODBC/src/Preparator.cpp

@@ -35,7 +35,7 @@ Preparator::Preparator(const StatementHandle& rStmt,
 {
 	SQLCHAR* pStr = (SQLCHAR*) statement.c_str();
 	if (Utility::isError(Poco::Data::ODBC::SQLPrepare(_rStmt, pStr, (SQLINTEGER) statement.length())))
-		throw StatementException(_rStmt);
+		throw StatementException(_rStmt, "ODBC::Preparator():SQLPrepare()");
 }
 
 
@@ -158,7 +158,8 @@ std::size_t Preparator::maxDataSize(std::size_t pos) const
 
 		// accomodate for terminating zero (non-bulk only!)
 		MetaColumn::ColumnDataType type = mc.type();
-		if (sz && !isBulk() && ((ODBCMetaColumn::FDT_WSTRING == type) || (ODBCMetaColumn::FDT_STRING == type))) ++sz;
+		if (sz && !isBulk() && ((ODBCMetaColumn::FDT_WSTRING == type) || (ODBCMetaColumn::FDT_STRING == type)))
+			++sz;
 	}
 	catch (StatementException&) { }
 
@@ -201,7 +202,7 @@ void Preparator::prepareBoolArray(std::size_t pos, SQLSMALLINT valueType, std::s
 		(SQLINTEGER) sizeof(bool),
 		&_lenLengths[pos][0])))
 	{
-		throw StatementException(_rStmt, "SQLBindCol()");
+		throw StatementException(_rStmt, "ODBC::Preparator::prepareBoolArray():SQLBindCol()");
 	}
 }
 

+ 98 - 62
Data/ODBC/src/SessionImpl.cpp

@@ -43,8 +43,10 @@ SessionImpl::SessionImpl(const std::string& connect,
 		_dbEncoding("UTF-8")
 {
 	setFeature("bulk", true);
+	// this option is obsolete; here only to support older drivers, should be changed to ODBC_CURSOR_USE_NEVER
+	// https://github.com/MicrosoftDocs/sql-docs/blob/live/docs/odbc/reference/appendixes/using-the-odbc-cursor-library.md
+	setCursorUse("", ODBC_CURSOR_USE_IF_NEEDED);
 	open();
-	setProperty("handle", _db.handle());
 }
 
 
@@ -63,8 +65,10 @@ SessionImpl::SessionImpl(const std::string& connect,
 		_dbEncoding("UTF-8")
 {
 	setFeature("bulk", true);
+	// this option is obsolete; here only to support older drivers, should be changed to ODBC_CURSOR_USE_NEVER
+	// https://github.com/MicrosoftDocs/sql-docs/blob/live/docs/odbc/reference/appendixes/using-the-odbc-cursor-library.md
+	setCursorUse("", ODBC_CURSOR_USE_IF_NEEDED);
 	open();
-	setProperty("handle", _db.handle());
 }
 
 
@@ -104,69 +108,51 @@ void SessionImpl::open(const std::string& connect)
 			setConnectionString(connect);
 	}
 
-	poco_assert_dbg (!connectionString().empty());
+	if (connectionString().empty())
+		throw InvalidArgumentException("SessionImpl::open(): Connection string empty");
 
 	SQLULEN tout = static_cast<SQLULEN>(getLoginTimeout());
-	if (Utility::isError(SQLSetConnectAttr(_db, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) tout, 0)))
-	{
-		if (Utility::isError(SQLGetConnectAttr(_db, SQL_ATTR_LOGIN_TIMEOUT, &tout, 0, 0)) ||
-				getLoginTimeout() != tout)
-		{
-			ConnectionError e(_db);
-			throw ConnectionFailedException(e.toString());
-		}
-	}
 
-	SQLCHAR connectOutput[512] = {0};
-	SQLSMALLINT result;
-
-	if (Utility::isError(Poco::Data::ODBC::SQLDriverConnect(_db
-		, NULL
-		,(SQLCHAR*) connectionString().c_str()
-		,(SQLSMALLINT) SQL_NTS
-		, connectOutput
-		, sizeof(connectOutput)
-		, &result
-		, SQL_DRIVER_NOPROMPT)))
+	if (_db.connect(connectionString(), tout))
 	{
-		ConnectionError err(_db);
-		std::string errStr = err.toString();
-		close();
-		throw ConnectionFailedException(errStr);
-	}
+		setProperty("handle", _db.handle());
 
-	_dataTypes.fillTypeInfo(_db);
-		addProperty("dataTypeInfo",
-		&SessionImpl::setDataTypeInfo,
-		&SessionImpl::dataTypeInfo);
+		_dataTypes.fillTypeInfo(_db.pHandle());
+			addProperty("dataTypeInfo",
+			&SessionImpl::setDataTypeInfo,
+			&SessionImpl::dataTypeInfo);
 
-	addFeature("autoCommit",
-		&SessionImpl::autoCommit,
-		&SessionImpl::isAutoCommit);
+		addFeature("autoCommit",
+			&SessionImpl::autoCommit,
+			&SessionImpl::isAutoCommit);
 
-	addFeature("autoBind",
-		&SessionImpl::autoBind,
-		&SessionImpl::isAutoBind);
+		addFeature("autoBind",
+			&SessionImpl::autoBind,
+			&SessionImpl::isAutoBind);
 
-	addFeature("autoExtract",
-		&SessionImpl::autoExtract,
-		&SessionImpl::isAutoExtract);
+		addFeature("autoExtract",
+			&SessionImpl::autoExtract,
+			&SessionImpl::isAutoExtract);
 
-	addProperty("maxFieldSize",
-		&SessionImpl::setMaxFieldSize,
-		&SessionImpl::getMaxFieldSize);
+		addProperty("maxFieldSize",
+			&SessionImpl::setMaxFieldSize,
+			&SessionImpl::getMaxFieldSize);
 
-	addProperty("queryTimeout",
-		&SessionImpl::setQueryTimeout,
-		&SessionImpl::getQueryTimeout);
+		addProperty("queryTimeout",
+			&SessionImpl::setQueryTimeout,
+			&SessionImpl::getQueryTimeout);
 
-	addProperty("dbEncoding",
-		&SessionImpl::setDBEncoding,
-		&SessionImpl::getDBEncoding);
+		addProperty("dbEncoding",
+			&SessionImpl::setDBEncoding,
+			&SessionImpl::getDBEncoding);
 
-	Poco::Data::ODBC::SQLSetConnectAttr(_db, SQL_ATTR_QUIET_MODE, 0, 0);
+		Poco::Data::ODBC::SQLSetConnectAttr(_db, SQL_ATTR_QUIET_MODE, 0, 0);
 
-	if (!canTransact()) autoCommit("", true);
+		if (!canTransact()) autoCommit("", true);
+	}
+	else
+		throw ConnectionException(SQL_NULL_HDBC,
+			Poco::format("Connection to '%s' failed.", connectionString()));
 }
 
 
@@ -180,15 +166,62 @@ void SessionImpl::setDBEncoding(const std::string&, const Poco::Any& value)
 
 bool SessionImpl::isConnected() const
 {
-	SQLULEN value = 0;
+	return _db.isConnected();
+}
 
-	if (Utility::isError(Poco::Data::ODBC::SQLGetConnectAttr(_db,
-		SQL_ATTR_CONNECTION_DEAD,
-		&value,
-		0,
-		0))) return false;
 
-	return (SQL_CD_FALSE == value);
+inline void SessionImpl::setCursorUse(const std::string&, const Poco::Any& value)
+{
+#if POCO_OS == POCO_OS_WINDOWS_NT
+#pragma warning (disable : 4995) // ignore marked as deprecated
+#endif
+	int cursorUse = static_cast<int>(Poco::AnyCast<CursorUse>(value));
+	int rc = 0;
+	switch (cursorUse)
+	{
+	case ODBC_CURSOR_USE_ALWAYS:
+		rc = Poco::Data::ODBC::SQLSetConnectAttr(_db, SQL_ATTR_ODBC_CURSORS, (SQLPOINTER)SQL_CUR_USE_ODBC, SQL_IS_INTEGER);
+		break;
+	case ODBC_CURSOR_USE_IF_NEEDED:
+		rc = Poco::Data::ODBC::SQLSetConnectAttr(_db, SQL_ATTR_ODBC_CURSORS, (SQLPOINTER)SQL_CUR_USE_IF_NEEDED, SQL_IS_INTEGER);
+		break;
+	case ODBC_CURSOR_USE_NEVER:
+		rc = Poco::Data::ODBC::SQLSetConnectAttr(_db, SQL_ATTR_ODBC_CURSORS, (SQLPOINTER)SQL_CUR_USE_DRIVER, SQL_IS_INTEGER);
+		break;
+	default:
+		throw Poco::InvalidArgumentException(Poco::format("SessionImpl::setCursorUse(%d)", cursorUse));
+	}
+#if POCO_OS == POCO_OS_WINDOWS_NT
+#pragma warning (default : 4995)
+#endif
+	if (Utility::isError(rc))
+	{
+		throw Poco::Data::ODBC::HandleException<SQLHDBC, SQL_HANDLE_DBC>(_db, Poco::format("SessionImpl::setCursorUse(%d)", cursorUse));
+	}
+}
+
+
+inline Poco::Any SessionImpl::getCursorUse(const std::string&) const
+{
+#if POCO_OS == POCO_OS_WINDOWS_NT
+#pragma warning (disable : 4995) // ignore marked as deprecated
+#endif
+	SQLUINTEGER curUse = 0;
+	Poco::Data::ODBC::SQLGetConnectAttr(_db, SQL_ATTR_ODBC_CURSORS, &curUse, SQL_IS_UINTEGER, 0);
+	switch (curUse)
+	{
+	case SQL_CUR_USE_ODBC:
+		return ODBC_CURSOR_USE_ALWAYS;
+	case SQL_CUR_USE_IF_NEEDED:
+		return ODBC_CURSOR_USE_IF_NEEDED;
+	case SQL_CUR_USE_DRIVER:
+		return ODBC_CURSOR_USE_NEVER;
+	default: break;
+	}
+	throw Poco::InvalidArgumentException(Poco::format("SessionImpl::getCursorUse(%u)", curUse));
+#if POCO_OS == POCO_OS_WINDOWS_NT
+#pragma warning (default : 4995)
+#endif
 }
 
 
@@ -333,12 +366,14 @@ void SessionImpl::autoCommit(const std::string&, bool val)
 		SQL_ATTR_AUTOCOMMIT,
 		val ? (SQLPOINTER) SQL_AUTOCOMMIT_ON :
 			(SQLPOINTER) SQL_AUTOCOMMIT_OFF,
-		SQL_IS_UINTEGER), "Failed to set automatic commit.");
+		SQL_IS_UINTEGER), "Failed to set autocommit.");
 }
 
 
 bool SessionImpl::isAutoCommit(const std::string&) const
 {
+	if (!_db) return false;
+
 	SQLULEN value = 0;
 
 	checkError(Poco::Data::ODBC::SQLGetConnectAttr(_db,
@@ -353,7 +388,7 @@ bool SessionImpl::isAutoCommit(const std::string&) const
 
 bool SessionImpl::isTransaction() const
 {
-	if (!canTransact()) return false;
+	if (!_db || !canTransact()) return false;
 
 	SQLULEN value = 0;
 	checkError(Poco::Data::ODBC::SQLGetConnectAttr(_db,
@@ -419,7 +454,8 @@ void SessionImpl::close()
 	{
 	}
 
-	SQLDisconnect(_db);
+	_db.disconnect();
+	setProperty("handle", SQL_NULL_HDBC);
 }
 
 

+ 6 - 5
Data/ODBC/src/TypeInfo.cpp

@@ -27,7 +27,7 @@ TypeInfo::TypeInfo(SQLHDBC* pHDBC): _pHDBC(pHDBC)
 {
 	fillCTypes();
 	fillSQLTypes();
-	if (_pHDBC) fillTypeInfo(*_pHDBC);
+	if (_pHDBC) fillTypeInfo(_pHDBC);
 }
 
 
@@ -62,7 +62,8 @@ void TypeInfo::fillCTypes()
 
 void TypeInfo::fillSQLTypes()
 {
-	_sqlDataTypes.insert(ValueType(SQL_C_CHAR, SQL_LONGVARCHAR));
+	_sqlDataTypes.insert(ValueType(SQL_C_CHAR, SQL_VARCHAR));
+	_sqlDataTypes.insert(ValueType(SQL_C_WCHAR, SQL_WVARCHAR));
 	_sqlDataTypes.insert(ValueType(SQL_C_BIT, SQL_BIT));
 	_sqlDataTypes.insert(ValueType(SQL_C_TINYINT, SQL_TINYINT));
 	_sqlDataTypes.insert(ValueType(SQL_C_STINYINT, SQL_TINYINT));
@@ -84,9 +85,9 @@ void TypeInfo::fillSQLTypes()
 }
 
 
-void TypeInfo::fillTypeInfo(SQLHDBC pHDBC)
+void TypeInfo::fillTypeInfo(const SQLHDBC* pHDBC)
 {
-	_pHDBC = &pHDBC;
+	_pHDBC = pHDBC;
 
 	if (_typeInfo.empty() && _pHDBC)
 	{
@@ -98,7 +99,7 @@ void TypeInfo::fillTypeInfo(SQLHDBC pHDBC)
 
 		rc = SQLAllocHandle(SQL_HANDLE_STMT, *_pHDBC, &hstmt);
 		if (!SQL_SUCCEEDED(rc))
-			throw StatementException(hstmt, "SQLGetData()");
+			throw StatementException(hstmt, "ODBC::Preparator::fillTypeInfo():SQLGetData()");
 
 		rc = SQLGetTypeInfo(hstmt, SQL_ALL_TYPES);
 		if (SQL_SUCCEEDED(rc))

+ 3 - 0
Data/ODBC/testsuite/src/ODBCDB2Test.cpp

@@ -594,6 +594,9 @@ CppUnit::Test* ODBCDB2Test::suite()
 		CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCDB2Test");
 
 		CppUnit_addTest(pSuite, ODBCDB2Test, testBareboneODBC);
+		CppUnit_addTest(pSuite, ODBCDB2Test, testConnection);
+		CppUnit_addTest(pSuite, ODBCDB2Test, testSession);
+		CppUnit_addTest(pSuite, ODBCDB2Test, testSessionPool);
 		CppUnit_addTest(pSuite, ODBCDB2Test, testZeroRows);
 		CppUnit_addTest(pSuite, ODBCDB2Test, testSimpleAccess);
 		CppUnit_addTest(pSuite, ODBCDB2Test, testComplexType);

+ 3 - 0
Data/ODBC/testsuite/src/ODBCMySQLTest.cpp

@@ -421,6 +421,9 @@ CppUnit::Test* ODBCMySQLTest::suite()
 		CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCMySQLTest");
 
 		CppUnit_addTest(pSuite, ODBCMySQLTest, testBareboneODBC);
+		CppUnit_addTest(pSuite, ODBCMySQLTest, testConnection);
+		CppUnit_addTest(pSuite, ODBCMySQLTest, testSession);
+		CppUnit_addTest(pSuite, ODBCMySQLTest, testSessionPool);
 		CppUnit_addTest(pSuite, ODBCMySQLTest, testZeroRows);
 		CppUnit_addTest(pSuite, ODBCMySQLTest, testSimpleAccess);
 		CppUnit_addTest(pSuite, ODBCMySQLTest, testComplexType);

+ 3 - 0
Data/ODBC/testsuite/src/ODBCOracleTest.cpp

@@ -854,6 +854,9 @@ CppUnit::Test* ODBCOracleTest::suite()
 		CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCOracleTest");
 
 		CppUnit_addTest(pSuite, ODBCOracleTest, testBareboneODBC);
+		CppUnit_addTest(pSuite, ODBCOracleTest, testConnection);
+		CppUnit_addTest(pSuite, ODBCOracleTest, testSession);
+		CppUnit_addTest(pSuite, ODBCOracleTest, testSessionPool);
 		CppUnit_addTest(pSuite, ODBCOracleTest, testZeroRows);
 		CppUnit_addTest(pSuite, ODBCOracleTest, testSimpleAccess);
 		CppUnit_addTest(pSuite, ODBCOracleTest, testComplexType);

+ 3 - 0
Data/ODBC/testsuite/src/ODBCPostgreSQLTest.cpp

@@ -583,6 +583,9 @@ CppUnit::Test* ODBCPostgreSQLTest::suite()
 		CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCPostgreSQLTest");
 
 		CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testBareboneODBC);
+		CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testConnection);
+		CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testSession);
+		CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testSessionPool);
 		CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testZeroRows);
 		CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testSimpleAccess);
 		CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testComplexType);

+ 399 - 285
Data/ODBC/testsuite/src/ODBCSQLServerTest.cpp

@@ -47,39 +47,26 @@ using Poco::DateTime;
 // uncomment to use native SQL driver
 //#define POCO_ODBC_USE_SQL_NATIVE
 
-// FreeTDS version selection guide (from http://www.freetds.org/userguide/choosingtdsprotocol.htm)
+// FreeTDS version selection guide: http://www.freetds.org/userguide/choosingtdsprotocol.htm
 // (see #define FREE_TDS_VERSION below)
-// Product												TDS Version	Comment
-// ---------------------------------------------------+------------+------------------------------------------------------------
-// Sybase before System 10, Microsoft SQL Server 6.x	4.2			Still works with all products, subject to its limitations.
-// Sybase System 10 and above							5.0			Still the most current protocol used by Sybase.
-// Sybase System SQL Anywhere							5.0 only 	Originally Watcom SQL Server, a completely separate codebase.
-// 																	Our best information is that SQL Anywhere first supported TDS
-// 																	in version 5.5.03 using the OpenServer Gateway (OSG), and native
-// 																	TDS 5.0 support arrived with version 6.0.
-// Microsoft SQL Server 7.0								7.0			Includes support for the extended datatypes in SQL Server 7.0
-// 																	(such as char/varchar fields of more than 255 characters), and
-// 																	support for Unicode.
-// Microsoft SQL Server 2000							8.0			Include support for bigint (64 bit integers), variant and collation
-// 																	on all fields. variant is not supported; collation is not widely used.
-
-#if defined(POCO_OS_FAMILY_WINDOWS) && !defined(FORCE_FREE_TDS)
+
+#if !defined(FORCE_FREE_TDS)
 	#ifdef POCO_ODBC_USE_SQL_NATIVE
 		#define MS_SQL_SERVER_ODBC_DRIVER "SQL Server Native Client 10.0"
 	#else
-		#define MS_SQL_SERVER_ODBC_DRIVER "SQL Server"
+		#define MS_SQL_SERVER_ODBC_DRIVER "ODBC Driver 18 for SQL Server"
 	#endif
 	#pragma message ("Using " MS_SQL_SERVER_ODBC_DRIVER " driver")
 #else
 	#define MS_SQL_SERVER_ODBC_DRIVER "FreeTDS"
-	#define FREE_TDS_VERSION "8.0"
+	#define FREE_TDS_VERSION "7.4"
 	#if defined(POCO_OS_FAMILY_WINDOWS)
 		#pragma message ("Using " MS_SQL_SERVER_ODBC_DRIVER " driver, version " FREE_TDS_VERSION)
 	#endif
 #endif
 
 #define MS_SQL_SERVER_DSN "PocoDataSQLServerTest"
-#define MS_SQL_SERVER_SERVER POCO_ODBC_TEST_DATABASE_SERVER "\\SQLEXPRESS"
+#define MS_SQL_SERVER_SERVER POCO_ODBC_TEST_DATABASE_SERVER
 #define MS_SQL_SERVER_PORT "1433"
 #define MS_SQL_SERVER_DB "poco"
 #define MS_SQL_SERVER_UID "poco"
@@ -101,6 +88,7 @@ std::string ODBCSQLServerTest::_connectString = "DRIVER=" MS_SQL_SERVER_ODBC_DRI
 	"DATABASE=" MS_SQL_SERVER_DB ";"
 	"SERVER=" MS_SQL_SERVER_SERVER ";"
 	"PORT=" MS_SQL_SERVER_PORT ";"
+	"Encrypt=no"
 #ifdef FREE_TDS_VERSION
 	"TDS_Version=" FREE_TDS_VERSION ";"
 #endif
@@ -120,7 +108,7 @@ ODBCSQLServerTest::~ODBCSQLServerTest()
 
 void ODBCSQLServerTest::testBareboneODBC()
 {
-	std::string tableCreateString = "CREATE TABLE Test "
+	std::string createString = "CREATE TABLE Test "
 		"(First VARCHAR(30),"
 		"Second VARCHAR(30),"
 		"Third VARBINARY(30),"
@@ -128,24 +116,47 @@ void ODBCSQLServerTest::testBareboneODBC()
 		"Fifth FLOAT,"
 		"Sixth DATETIME)";
 
-	executor().bareboneODBCTest(dbConnString(), tableCreateString,
+	executor().bareboneODBCTest(dbConnString(), createString,
 		SQLExecutor::PB_IMMEDIATE, SQLExecutor::DE_MANUAL, true, "CONVERT(VARBINARY(30),?)");
-	executor().bareboneODBCTest(dbConnString(), tableCreateString,
+	executor().bareboneODBCTest(dbConnString(), createString,
 		SQLExecutor::PB_IMMEDIATE, SQLExecutor::DE_BOUND, true, "CONVERT(VARBINARY(30),?)");
-	executor().bareboneODBCTest(dbConnString(), tableCreateString,
+	executor().bareboneODBCTest(dbConnString(), createString,
 		SQLExecutor::PB_AT_EXEC, SQLExecutor::DE_MANUAL, true, "CONVERT(VARBINARY(30),?)");
-	executor().bareboneODBCTest(dbConnString(), tableCreateString,
+	executor().bareboneODBCTest(dbConnString(), createString,
 		SQLExecutor::PB_AT_EXEC, SQLExecutor::DE_BOUND, true, "CONVERT(VARBINARY(30),?)");
 
-	tableCreateString = "CREATE TABLE Test "
+	createString = "CREATE TABLE Test "
 		"(First VARCHAR(30),"
 		"Second INTEGER,"
 		"Third FLOAT)";
 
-	executor().bareboneODBCMultiResultTest(dbConnString(), tableCreateString, SQLExecutor::PB_IMMEDIATE, SQLExecutor::DE_MANUAL);
-	executor().bareboneODBCMultiResultTest(dbConnString(), tableCreateString, SQLExecutor::PB_IMMEDIATE, SQLExecutor::DE_BOUND);
-	executor().bareboneODBCMultiResultTest(dbConnString(), tableCreateString, SQLExecutor::PB_AT_EXEC, SQLExecutor::DE_MANUAL);
-	executor().bareboneODBCMultiResultTest(dbConnString(), tableCreateString, SQLExecutor::PB_AT_EXEC, SQLExecutor::DE_BOUND);
+	executor().bareboneODBCMultiResultTest(dbConnString(), createString, SQLExecutor::PB_IMMEDIATE, SQLExecutor::DE_MANUAL);
+	executor().bareboneODBCMultiResultTest(dbConnString(), createString, SQLExecutor::PB_IMMEDIATE, SQLExecutor::DE_BOUND);
+	executor().bareboneODBCMultiResultTest(dbConnString(), createString, SQLExecutor::PB_AT_EXEC, SQLExecutor::DE_MANUAL);
+	executor().bareboneODBCMultiResultTest(dbConnString(), createString, SQLExecutor::PB_AT_EXEC, SQLExecutor::DE_BOUND);
+
+	dropObject("PROCEDURE", "TestStoredProcedure");
+	createString = "CREATE PROCEDURE TestStoredProcedure(@inParam VARCHAR(MAX), @outParam VARCHAR(MAX) OUTPUT) AS "
+		"BEGIN "
+		" DECLARE @retVal int;"
+		" SET @outParam = @inParam; "
+		" SET @retVal = @outParam;"
+		" RETURN @retVal;"
+		"END;";
+
+	std::string execString = "{? = CALL TestStoredProcedure(?, ?)}";
+
+	executor().bareboneODBCStoredFuncTest(dbConnString(), createString, execString, SQLExecutor::PB_IMMEDIATE, SQLExecutor::DE_MANUAL);
+	executor().bareboneODBCStoredFuncTest(dbConnString(), createString, execString, SQLExecutor::PB_IMMEDIATE, SQLExecutor::DE_BOUND);
+	// data at exec fails for the SNAC driver - for some reason, SQLParamData() never reports the return parameter
+	// newer drivers work fine
+	if (std::string(MS_SQL_SERVER_ODBC_DRIVER) != "SQL Server")
+	{
+		executor().bareboneODBCStoredFuncTest(dbConnString(), createString, execString, SQLExecutor::PB_AT_EXEC, SQLExecutor::DE_MANUAL);
+		executor().bareboneODBCStoredFuncTest(dbConnString(), createString, execString, SQLExecutor::PB_AT_EXEC, SQLExecutor::DE_BOUND);
+	}
+	else
+		std::cout << "Parameter at exec binding tests disabled for " << MS_SQL_SERVER_ODBC_DRIVER << std::endl;
 }
 
 void ODBCSQLServerTest::testTempTable()
@@ -161,13 +172,14 @@ void ODBCSQLServerTest::testTempTable()
 	std::vector<testParam> testParams;
 	session() << ("select * from #test;"), into(testParams), now;
 
-	assertEquals(1, testParams.size());
+	assertEquals(1, static_cast<long>(testParams.size()));
 
 	assertEquals(1, testParams.front().get<0>());
 	assertEquals(2, testParams.front().get<1>());
 	assertEquals(3, testParams.front().get<2>());
 }
 
+
 void ODBCSQLServerTest::testBLOB()
 {
 	const std::size_t maxFldSize = 250000;
@@ -262,285 +274,386 @@ void ODBCSQLServerTest::testBulk()
 
 void ODBCSQLServerTest::testStoredProcedure()
 {
-	for (int k = 0; k < 8;)
+	try
 	{
-		session().setFeature("autoBind", bindValue(k));
-		session().setFeature("autoExtract", bindValue(k+1));
-
-		dropObject("PROCEDURE", "storedProcedure");
-
-		session() << "CREATE PROCEDURE storedProcedure(@outParam int OUTPUT) AS "
-			"BEGIN "
-			"SET @outParam = -1; "
-			"END;"
-		, now;
-
-		int i = 0;
-		session() << "{call storedProcedure(?)}", out(i), now;
-		assertTrue (-1 == i);
-		dropObject("PROCEDURE", "storedProcedure");
-
-		session() << "CREATE PROCEDURE storedProcedure(@inParam int, @outParam int OUTPUT) AS "
-			"BEGIN "
-			"SET @outParam = @inParam*@inParam; "
-			"END;"
-		, now;
-
-		i = 2;
-		int j = 0;
-		session() << "{call storedProcedure(?, ?)}", in(i), out(j), now;
-		assertTrue (4 == j);
-		dropObject("PROCEDURE", "storedProcedure");
-
-		session() << "CREATE PROCEDURE storedProcedure(@ioParam int OUTPUT) AS "
-			"BEGIN "
-			"SET @ioParam = @ioParam*@ioParam; "
-			"END;"
-		, now;
-
-		i = 2;
-		session() << "{call storedProcedure(?)}", io(i), now;
-		assertTrue (4 == i);
-		dropObject("PROCEDURE", "storedProcedure");
-
-		session() << "CREATE PROCEDURE storedProcedure(@ioParam DATETIME OUTPUT) AS "
-			"BEGIN "
-			" SET @ioParam = @ioParam + 1; "
-			"END;" , now;
-
-		DateTime dt(1965, 6, 18, 5, 35, 1);
-		session() << "{call storedProcedure(?)}", io(dt), now;
-		assertTrue (19 == dt.day());
+		for (int k = 0; k < 8;)
+		{
+			session().setFeature("autoBind", bindValue(k));
+			session().setFeature("autoExtract", bindValue(k + 1));
+
+			dropObject("PROCEDURE", "storedProcedure");
+
+			session() << "CREATE PROCEDURE storedProcedure(@outParam int OUTPUT) AS "
+				"BEGIN "
+				" SET @outParam = -1; "
+				"END;"
+			, now;
+
+			int i = 0;
+			session() << "{call storedProcedure(?)}", out(i), now;
+			assertTrue (-1 == i);
+
+			dropObject("PROCEDURE", "storedProcedure");
+			session() << "CREATE PROCEDURE storedProcedure(@inParam int, @outParam int OUTPUT) AS "
+				"BEGIN "
+				" SET @outParam = @inParam*@inParam; "
+				"END;"
+			, now;
+
+			i = 2;
+			int j = 0;
+			session() << "{call storedProcedure(?, ?)}", in(i), out(j), now;
+			assertTrue (4 == j);
+
+			dropObject("PROCEDURE", "storedProcedure");
+			session() << "CREATE PROCEDURE storedProcedure(@ioParam int OUTPUT) AS "
+				"BEGIN "
+				" SET @ioParam = @ioParam*@ioParam; "
+				"END;"
+			, now;
+
+			i = 2;
+			session() << "{call storedProcedure(?)}", io(i), now;
+			assertTrue (4 == i);
+			dropObject("PROCEDURE", "storedProcedure");
+
+			session() << "CREATE PROCEDURE storedProcedure(@ioParam DATETIME OUTPUT) AS "
+				"BEGIN "
+				" SET @ioParam = @ioParam + 1; "
+				"END;" , now;
+
+			DateTime dt(1965, 6, 18, 5, 35, 1);
+			session() << "{call storedProcedure(?)}", io(dt), now;
+			assertTrue (19 == dt.day());
+			dropObject("PROCEDURE", "storedProcedure");
+
+			session().setFeature("autoBind", true);
+			session() << "CREATE PROCEDURE storedProcedure(@inParam VARCHAR(MAX), @outParam VARCHAR(MAX) OUTPUT) AS "
+				"BEGIN "
+				" SET @outParam = @inParam; "
+				"END;"
+				, now;
+
+			std::string inParam = "123";
+			std::string outParam(4, 0);
+			session() << "{call storedProcedure(?, ?)}", in(inParam), out(outParam), now;
+			assertTrue(outParam == inParam);
+
+			k += 2;
+		}
 		dropObject("PROCEDURE", "storedProcedure");
-
-		k += 2;
 	}
-/*TODO - currently fails with following error:
-
-[Microsoft][ODBC SQL Server Driver][SQL Server]Invalid parameter
-2 (''):  Data type 0x23 is a deprecated large object, or LOB, but is marked as output parameter.
-Deprecated types are not supported as output parameters.  Use current large object types instead.
-
-	session().setFeature("autoBind", true);
-	session() << "CREATE PROCEDURE storedProcedure(@inParam VARCHAR(MAX), @outParam VARCHAR(MAX) OUTPUT) AS "
-		"BEGIN "
-		"SET @outParam = @inParam; "
-		"END;"
-	, now;
-
-	std::string inParam = "123";
-	std::string outParam;
-	try{
-	session() << "{call storedProcedure(?, ?)}", in(inParam), out(outParam), now;
-	}catch(StatementException& ex){std::cout << ex.toString();}
-	assertTrue (outParam == inParam);
-	dropObject("PROCEDURE", "storedProcedure");
-	*/
+	catch (ConnectionException& ce) { std::cout << ce.toString() << std::endl; fail("testStoredProcedure()"); }
+	catch (StatementException& se) { std::cout << se.toString() << std::endl; fail("testStoredProcedure()"); }
 }
 
 
 void ODBCSQLServerTest::testCursorStoredProcedure()
 {
-	for (int k = 0; k < 8;)
+	try
 	{
-		session().setFeature("autoBind", bindValue(k));
-		session().setFeature("autoExtract", bindValue(k+1));
-
-		recreatePersonTable();
-		typedef Tuple<std::string, std::string, std::string, int> Person;
-		std::vector<Person> people;
-		people.push_back(Person("Simpson", "Homer", "Springfield", 42));
-		people.push_back(Person("Simpson", "Bart", "Springfield", 12));
-		people.push_back(Person("Simpson", "Lisa", "Springfield", 10));
-		session() << "INSERT INTO Person VALUES (?, ?, ?, ?)", use(people), now;
-
-		dropObject("PROCEDURE", "storedCursorProcedure");
-		session() << "CREATE PROCEDURE storedCursorProcedure(@ageLimit int) AS "
-			"BEGIN "
-			" SELECT * "
-			" FROM Person "
-			" WHERE Age < @ageLimit "
-			" ORDER BY Age DESC; "
-			"END;"
-		, now;
-
-		people.clear();
-		int age = 13;
-
-		session() << "{call storedCursorProcedure(?)}", in(age), into(people), now;
-
-		assertTrue (2 == people.size());
-		assertTrue (Person("Simpson", "Bart", "Springfield", 12) == people[0]);
-		assertTrue (Person("Simpson", "Lisa", "Springfield", 10) == people[1]);
-
-		Statement stmt = ((session() << "{call storedCursorProcedure(?)}", in(age), now));
-		RecordSet rs(stmt);
-		assertTrue (rs["LastName"] == "Simpson");
-		assertTrue (rs["FirstName"] == "Bart");
-		assertTrue (rs["Address"] == "Springfield");
-		assertTrue (rs["Age"] == 12);
-
-		dropObject("TABLE", "Person");
+		for (int k = 0; k < 8;)
+		{
+			session().setFeature("autoBind", bindValue(k));
+			session().setFeature("autoExtract", bindValue(k+1));
+
+			recreatePersonTable();
+			typedef Tuple<std::string, std::string, std::string, int> Person;
+			std::vector<Person> people;
+			people.push_back(Person("Simpson", "Homer", "Springfield", 42));
+			people.push_back(Person("Simpson", "Bart", "Springfield", 12));
+			people.push_back(Person("Simpson", "Lisa", "Springfield", 10));
+			session() << "INSERT INTO Person VALUES (?, ?, ?, ?)", use(people), now;
+
+			dropObject("PROCEDURE", "storedCursorProcedure");
+			session() << "CREATE PROCEDURE storedCursorProcedure(@ageLimit int) AS "
+				"BEGIN "
+				" SELECT * "
+				" FROM Person "
+				" WHERE Age < @ageLimit "
+				" ORDER BY Age DESC; "
+				"END;"
+			, now;
+
+			people.clear();
+			int age = 13;
+
+			session() << "{call storedCursorProcedure(?)}", in(age), into(people), now;
+
+			assertTrue (2 == people.size());
+			assertTrue (Person("Simpson", "Bart", "Springfield", 12) == people[0]);
+			assertTrue (Person("Simpson", "Lisa", "Springfield", 10) == people[1]);
+
+			Statement stmt = ((session() << "{call storedCursorProcedure(?)}", in(age), now));
+			RecordSet rs(stmt);
+			assertTrue (rs["LastName"] == "Simpson");
+			assertTrue (rs["FirstName"] == "Bart");
+			assertTrue (rs["Address"] == "Springfield");
+			assertTrue (rs["Age"] == 12);
+
+			dropObject("PROCEDURE", "storedCursorProcedure");
+
+			// procedure that suppresses row counts and recordsets
+			session() << "CREATE PROCEDURE storedCursorProcedure(@outStr varchar(64) OUTPUT) AS "
+				"BEGIN "
+				" SET NOCOUNT ON;"
+				" DECLARE @PersonTable TABLE (LastName VARCHAR(30), FirstName VARCHAR(30), Address VARCHAR(30), Age INTEGER); "
+				" INSERT INTO @PersonTable SELECT * FROM Person; "
+				" UPDATE Person SET FirstName = 'Dart' WHERE FirstName = 'Bart';"
+				" SELECT @outStr = FirstName FROM Person WHERE Age = 12;"
+				" RETURN -1;"
+				"END;"
+				, now;
+
+			std::string outStr(64, 0);
+			int ret = 0;
+			session() << "{? = call storedCursorProcedure(?)}", out(ret), out(outStr), now;
+			assertTrue(ret == -1);
+			assertTrue(outStr == "Dart");
+
+			dropObject("PROCEDURE", "storedCursorProcedure");
+
+			// procedure that suppresses row counts and recordsets
+			session() << "CREATE PROCEDURE storedCursorProcedure(@name varchar(30)) AS "
+				"BEGIN "
+				" SET NOCOUNT ON;"
+				" DECLARE @count int; "
+				" SELECT @count = count(*) FROM Person WHERE FirstName = @name;"
+				" RETURN @count;"
+				"END;"
+				, now;
+
+			std::string name = "Dart";
+			ret = 0;
+			session() << "{? = call storedCursorProcedure(?)}", out(ret), in(name), now;
+			assertTrue(ret == 1);
+
+			dropObject("TABLE", "Person");
+
+			k += 2;
+		}
 		dropObject("PROCEDURE", "storedCursorProcedure");
-
-		k += 2;
 	}
+	catch (ConnectionException& ce) { std::cout << ce.toString() << std::endl; fail("testCursorStoredProcedure()"); }
+	catch (StatementException& se) { std::cout << se.toString() << std::endl; fail("testCursorStoredProcedure()"); }
 }
 
 
 void ODBCSQLServerTest::testStoredProcedureAny()
 {
-	for (int k = 0; k < 8;)
+	try
 	{
-		session().setFeature("autoBind", bindValue(k));
-		session().setFeature("autoExtract", bindValue(k+1));
-
-		Any i = 2;
-		Any j = 0;
-
-		session() << "CREATE PROCEDURE storedProcedure(@inParam int, @outParam int OUTPUT) AS "
-			"BEGIN "
-			"SET @outParam = @inParam*@inParam; "
-			"END;"
-		, now;
-
-		session() << "{call storedProcedure(?, ?)}", in(i), out(j), now;
-		assertTrue (4 == AnyCast<int>(j));
-		session() << "DROP PROCEDURE storedProcedure;", now;
-
-		session() << "CREATE PROCEDURE storedProcedure(@ioParam int OUTPUT) AS "
-			"BEGIN "
-			"SET @ioParam = @ioParam*@ioParam; "
-			"END;"
-		, now;
-
-		i = 2;
-		session() << "{call storedProcedure(?)}", io(i), now;
-		assertTrue (4 == AnyCast<int>(i));
+		for (int k = 0; k < 8;)
+		{
+			session().setFeature("autoBind", bindValue(k));
+			session().setFeature("autoExtract", bindValue(k+1));
+
+			Any i = 2;
+			Any j = 0;
+
+			dropObject("PROCEDURE", "storedProcedure");
+			session() << "CREATE PROCEDURE storedProcedure(@inParam int, @outParam int OUTPUT) AS "
+				"BEGIN "
+				"SET @outParam = @inParam*@inParam; "
+				"END;"
+			, now;
+
+			session() << "{call storedProcedure(?, ?)}", in(i), out(j), now;
+			assertTrue (4 == AnyCast<int>(j));
+
+			dropObject("PROCEDURE", "storedProcedure");
+			session() << "CREATE PROCEDURE storedProcedure(@ioParam int OUTPUT) AS "
+				"BEGIN "
+				"SET @ioParam = @ioParam*@ioParam; "
+				"END;"
+			, now;
+
+			i = 2;
+			session() << "{call storedProcedure(?)}", io(i), now;
+			assertTrue (4 == AnyCast<int>(i));
+			dropObject("PROCEDURE", "storedProcedure");
+
+			k += 2;
+		}
 		dropObject("PROCEDURE", "storedProcedure");
-
-		k += 2;
 	}
+	catch (ConnectionException& ce) { std::cout << ce.toString() << std::endl; fail("testStoredProcedureAny()"); }
+	catch (StatementException& se) { std::cout << se.toString() << std::endl; fail("testStoredProcedureAny()"); }
 }
 
 
 void ODBCSQLServerTest::testStoredProcedureDynamicAny()
 {
-	for (int k = 0; k < 8;)
+	try
 	{
-		session().setFeature("autoBind", bindValue(k));
-
-		DynamicAny i = 2;
-		DynamicAny j = 0;
-
-		session() << "CREATE PROCEDURE storedProcedure(@inParam int, @outParam int OUTPUT) AS "
-			"BEGIN "
-			"SET @outParam = @inParam*@inParam; "
-			"END;"
-		, now;
-
-		session() << "{call storedProcedure(?, ?)}", in(i), out(j), now;
-		assertTrue (4 == j);
-		session() << "DROP PROCEDURE storedProcedure;", now;
-
-		session() << "CREATE PROCEDURE storedProcedure(@ioParam int OUTPUT) AS "
-			"BEGIN "
-			"SET @ioParam = @ioParam*@ioParam; "
-			"END;"
-		, now;
-
-		i = 2;
-		session() << "{call storedProcedure(?)}", io(i), now;
-		assertTrue (4 == i);
+		for (int k = 0; k < 8;)
+		{
+			session().setFeature("autoBind", bindValue(k));
+
+			DynamicAny i = 2;
+			DynamicAny j = 0;
+
+			dropObject("PROCEDURE", "storedProcedure");
+			session() << "CREATE PROCEDURE storedProcedure(@inParam int, @outParam int OUTPUT) AS "
+				"BEGIN "
+				"SET @outParam = @inParam*@inParam; "
+				"END;"
+			, now;
+
+			session() << "{call storedProcedure(?, ?)}", in(i), out(j), now;
+			assertTrue (4 == j);
+
+			dropObject("PROCEDURE", "storedProcedure");
+			session() << "CREATE PROCEDURE storedProcedure(@ioParam int OUTPUT) AS "
+				"BEGIN "
+				"SET @ioParam = @ioParam*@ioParam; "
+				"END;"
+			, now;
+
+			i = 2;
+			session() << "{call storedProcedure(?)}", io(i), now;
+			assertTrue (4 == i);
+
+			k += 2;
+		}
 		dropObject("PROCEDURE", "storedProcedure");
+	}
+	catch (ConnectionException& ce) { std::cout << ce.toString() << std::endl; fail("testStoredProcedureDynamicAny()"); }
+	catch (StatementException& se) { std::cout << se.toString() << std::endl; fail("testStoredProcedureDynamicAny()"); }
+}
+
 
-		k += 2;
+void ODBCSQLServerTest::testStoredProcedureReturn()
+{
+	try
+	{
+		for (int k = 0; k < 8;)
+		{
+			session().setFeature("autoBind", bindValue(k));
+			session().setFeature("autoExtract", bindValue(k+1));
+
+			dropObject("PROCEDURE", "storedProcedureReturn");
+			session() << "CREATE PROCEDURE storedProcedureReturn AS "
+				"BEGIN "
+				"DECLARE @retVal int;"
+				"SET @retVal = -1;"
+				"RETURN @retVal;"
+				"END;"
+			, now;
+
+			int i = 0;
+			session() << "{? = call storedProcedureReturn}", out(i), now;
+			assertTrue (-1 == i);
+
+			dropObject("PROCEDURE", "storedProcedureReturn");
+			session() << "CREATE PROCEDURE storedProcedureReturn(@inParam int) AS "
+				"BEGIN "
+				"RETURN @inParam*@inParam;"
+				"END;"
+			, now;
+
+			i = 2;
+			int result = 0;
+			session() << "{? = call storedProcedureReturn(?)}", out(result), in(i), now;
+			assertTrue (4 == result);
+
+			dropObject("PROCEDURE", "storedProcedureReturn");
+			session() << "CREATE PROCEDURE storedProcedureReturn(@inParam int, @outParam int OUTPUT) AS "
+				"BEGIN "
+				"SET @outParam = @inParam*@inParam;"
+				"RETURN @outParam;"
+				"END"
+			, now;
+
+			i = 2;
+			int j = 0;
+			result = 0;
+			session() << "{? = call storedProcedureReturn(?, ?)}", out(result), in(i), out(j), now;
+			assertTrue (4 == j);
+			assertTrue (j == result);
+
+			dropObject("PROCEDURE", "storedProcedureReturn");
+			session() << "CREATE PROCEDURE storedProcedureReturn(@param1 int OUTPUT,@param2 int OUTPUT) AS "
+				"BEGIN "
+				"DECLARE @temp int; "
+				"SET @temp = @param1;"
+				"SET @param1 = @param2;"
+				"SET @param2 = @temp;"
+				"RETURN @param1 + @param2; "
+				"END"
+			, now;
+
+			i = 1;
+			j = 2;
+			result = 0;
+			session() << "{? = call storedProcedureReturn(?, ?)}", out(result), io(i), io(j), now;
+			assertTrue (1 == j);
+			assertTrue (2 == i);
+			assertTrue (3 == result);
+
+			Tuple<int, int> params(1, 2);
+			assertTrue (1 == params.get<0>());
+			assertTrue (2 == params.get<1>());
+			result = 0;
+			session() << "{? = call storedProcedureReturn(?, ?)}", out(result), io(params), now;
+			assertTrue (1 == params.get<1>());
+			assertTrue (2 == params.get<0>());
+			assertTrue (3 == result);
+
+			k += 2;
+		}
+		dropObject("PROCEDURE", "storedProcedureReturn");
 	}
+	catch (ConnectionException& ce) { std::cout << ce.toString() << std::endl; fail("testStoredProcedureReturn()"); }
+	catch (StatementException& se) { std::cout << se.toString() << std::endl; fail("testStoredProcedureReturn()"); }
 }
 
 
 void ODBCSQLServerTest::testStoredFunction()
 {
-	for (int k = 0; k < 8;)
+	try
 	{
-		session().setFeature("autoBind", bindValue(k));
-		session().setFeature("autoExtract", bindValue(k+1));
-
-		dropObject("PROCEDURE", "storedFunction");
-		session() << "CREATE PROCEDURE storedFunction AS "
-			"BEGIN "
-			"DECLARE @retVal int;"
-			"SET @retVal = -1;"
-			"RETURN @retVal;"
-			"END;"
-		, now;
-
-		int i = 0;
-		session() << "{? = call storedFunction}", out(i), now;
-		assertTrue (-1 == i);
-		dropObject("PROCEDURE", "storedFunction");
-
-
-		session() << "CREATE PROCEDURE storedFunction(@inParam int) AS "
-			"BEGIN "
-			"RETURN @inParam*@inParam;"
-			"END;"
-		, now;
-
-		i = 2;
-		int result = 0;
-		session() << "{? = call storedFunction(?)}", out(result), in(i), now;
-		assertTrue (4 == result);
-		dropObject("PROCEDURE", "storedFunction");
-
-
-		session() << "CREATE PROCEDURE storedFunction(@inParam int, @outParam int OUTPUT) AS "
-			"BEGIN "
-			"SET @outParam = @inParam*@inParam;"
-			"RETURN @outParam;"
-			"END"
-		, now;
-
-		i = 2;
-		int j = 0;
-		result = 0;
-		session() << "{? = call storedFunction(?, ?)}", out(result), in(i), out(j), now;
-		assertTrue (4 == j);
-		assertTrue (j == result);
-		dropObject("PROCEDURE", "storedFunction");
-
-
-		session() << "CREATE PROCEDURE storedFunction(@param1 int OUTPUT,@param2 int OUTPUT) AS "
-			"BEGIN "
-			"DECLARE @temp int; "
-			"SET @temp = @param1;"
-			"SET @param1 = @param2;"
-			"SET @param2 = @temp;"
-			"RETURN @param1 + @param2; "
-			"END"
-		, now;
-
-		i = 1;
-		j = 2;
-		result = 0;
-		session() << "{? = call storedFunction(?, ?)}", out(result), io(i), io(j), now;
-		assertTrue (1 == j);
-		assertTrue (2 == i);
-		assertTrue (3 == result);
-
-		Tuple<int, int> params(1, 2);
-		assertTrue (1 == params.get<0>());
-		assertTrue (2 == params.get<1>());
-		result = 0;
-		session() << "{? = call storedFunction(?, ?)}", out(result), io(params), now;
-		assertTrue (1 == params.get<1>());
-		assertTrue (2 == params.get<0>());
-		assertTrue (3 == result);
-
-		dropObject("PROCEDURE", "storedFunction");
-
-		k += 2;
+		for (int k = 0; k < 8;)
+		{
+			session().setFeature("autoBind", bindValue(k));
+			session().setFeature("autoExtract", bindValue(k + 1));
+
+			dropObject("FUNCTION", "storedFunction");
+			session() << "CREATE FUNCTION storedFunction() "
+				" RETURNS int AS "
+				"BEGIN "
+				" DECLARE @retVal int;"
+				" SET @retVal = -1;"
+				" RETURN @retVal;"
+				"END;"
+				, now;
+
+			int i = 0;
+			session() << "{? = call dbo.storedFunction}", out(i), now;
+			assertTrue(-1 == i);
+
+			dropObject("FUNCTION", "storedFunction");
+			session() << "CREATE FUNCTION storedFunction(@inParam int) "
+				"RETURNS int AS "
+				"BEGIN "
+				"  RETURN @inParam*@inParam;"
+				"END;"
+				, now;
+
+			i = 2;
+			int result = 0;
+			session() << "{? = call dbo.storedFunction(?)}", out(result), in(i), now;
+			assertTrue(4 == result);
+			result = 0;
+			session() << "SELECT dbo.storedFunction(?)", into(result), in(i), now;
+			assertTrue(4 == result);
+
+			k += 2;
+		}
+		dropObject("FUNCTION", "storedFunction");
 	}
+	catch (ConnectionException& ce) { std::cout << ce.toString() << std::endl; fail("testStoredFunction()"); }
+	catch (StatementException& se) { std::cout << se.toString() << std::endl; fail("testStoredFunction()"); }
 }
 
 
@@ -552,19 +665,15 @@ void ODBCSQLServerTest::dropObject(const std::string& type, const std::string& n
 	}
 	catch (StatementException& ex)
 	{
-		bool ignoreError = false;
 		const StatementDiagnostics::FieldVec& flds = ex.diagnostics().fields();
 		StatementDiagnostics::Iterator it = flds.begin();
 		for (; it != flds.end(); ++it)
 		{
-			if (3701 == it->_nativeError)//(table does not exist)
-			{
-				ignoreError = true;
-				break;
-			}
+			if (3701 == it->_nativeError) // (does not exist)
+				return;
 		}
 
-		if (!ignoreError) throw;
+		throw;
 	}
 }
 
@@ -725,13 +834,13 @@ void ODBCSQLServerTest::recreateLogTable()
 	try
 	{
 		std::string sql = "CREATE TABLE %s "
-			"(Source VARCHAR(max),"
-			"Name VARCHAR(max),"
+			"(Source VARCHAR(256),"
+			"Name VARCHAR(256),"
 			"ProcessId INTEGER,"
-			"Thread VARCHAR(max), "
+			"Thread VARCHAR(256), "
 			"ThreadId INTEGER,"
 			"Priority INTEGER,"
-			"Text VARCHAR(max),"
+			"Text VARCHAR(1024),"
 			"DateTime DATETIME)";
 
 		session() << sql, "T_POCO_LOG", now;
@@ -779,6 +888,9 @@ CppUnit::Test* ODBCSQLServerTest::suite()
 		CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCSQLServerTest");
 
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testBareboneODBC);
+		CppUnit_addTest(pSuite, ODBCSQLServerTest, testConnection);
+		CppUnit_addTest(pSuite, ODBCSQLServerTest, testSession);
+		CppUnit_addTest(pSuite, ODBCSQLServerTest, testSessionPool);
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testZeroRows);
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testSimpleAccess);
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testComplexType);
@@ -828,6 +940,7 @@ CppUnit::Test* ODBCSQLServerTest::suite()
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testBLOB);
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testBLOBContainer);
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testBLOBStmt);
+		CppUnit_addTest(pSuite, ODBCSQLServerTest, testRecordSet);
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testDateTime);
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testFloat);
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testDouble);
@@ -838,6 +951,7 @@ CppUnit::Test* ODBCSQLServerTest::suite()
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testCursorStoredProcedure);
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testStoredProcedureAny);
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testStoredProcedureDynamicAny);
+		CppUnit_addTest(pSuite, ODBCSQLServerTest, testStoredProcedureReturn);
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testStoredFunction);
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testInternalExtraction);
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testFilter);

+ 9 - 7
Data/ODBC/testsuite/src/ODBCSQLServerTest.h

@@ -26,13 +26,14 @@ class ODBCSQLServerTest: public ODBCTest
 	/// SQLServer ODBC test class
 	/// Tested:
 	///
-	/// Driver				|	DB								| OS
-	/// --------------------+-----------------------------------+------------------------------------------
-	/// 2000.86.1830.00		| SQL Server Express 9.0.2047		| MS Windows XP Professional x64 v.2003/SP1
-	/// 2005.90.2047.00		| SQL Server Express 9.0.2047		| MS Windows XP Professional x64 v.2003/SP1
-	/// 2009.100.1600.01	| SQL Server Express 10.50.1600.1	| MS Windows XP Professional x64 v.2003/SP1
-	///
-
+	///  Driver name                        | Driver version    | DB version            | OS
+	/// ------------------------------------+-------------------+-----------------------+------------------------------------------
+	///  SQL Server Express 9.0.2047        | 2000.86.1830.00   |                       | MS Windows XP Professional x64 v.2003/SP1
+	///  SQL Server Express 9.0.2047        | 2005.90.2047.00   |                       | MS Windows XP Professional x64 v.2003/SP1
+	///  SQL Server Express 10.50.1600.1    | 2009.100.1600.01  |                       | MS Windows XP Professional x64 v.2003/SP1
+	///  SQL Server                         | 10.00.22621.1992  | 16.0.1000.6 (64-bit)  | Windows 11
+	///  ODBC Driver 17 for SQL Server      | 2017.1710.03.01   | 16.0.1000.6 (64-bit)  | Windows 11
+	///  ODBC Driver 18 for SQL Server      | 2018.183.01.01    | 16.0.1000.6 (64-bit)  | Windows 11
 {
 public:
 	ODBCSQLServerTest(const std::string& name);
@@ -51,6 +52,7 @@ public:
 	void testStoredProcedureAny();
 	void testStoredProcedureDynamicAny();
 
+	void testStoredProcedureReturn();
 	void testStoredFunction();
 
 	static CppUnit::Test* suite();

+ 3 - 0
Data/ODBC/testsuite/src/ODBCSQLiteTest.cpp

@@ -323,6 +323,9 @@ CppUnit::Test* ODBCSQLiteTest::suite()
 		CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCSQLiteTest");
 
 		CppUnit_addTest(pSuite, ODBCSQLiteTest, testBareboneODBC);
+		CppUnit_addTest(pSuite, ODBCSQLiteTest, testConnection);
+		CppUnit_addTest(pSuite, ODBCSQLiteTest, testSession);
+		CppUnit_addTest(pSuite, ODBCSQLiteTest, testSessionPool);
 		CppUnit_addTest(pSuite, ODBCSQLiteTest, testZeroRows);
 		CppUnit_addTest(pSuite, ODBCSQLiteTest, testSimpleAccess);
 		CppUnit_addTest(pSuite, ODBCSQLiteTest, testComplexType);

+ 34 - 1
Data/ODBC/testsuite/src/ODBCTest.cpp

@@ -77,6 +77,24 @@ ODBCTest::~ODBCTest()
 }
 
 
+void ODBCTest::testConnection()
+{
+	_pExecutor->connection(_rConnectString);
+}
+
+
+void ODBCTest::testSession()
+{
+	_pExecutor->session(_rConnectString, 5);
+}
+
+
+void ODBCTest::testSessionPool()
+{
+	_pExecutor->sessionPool(_rConnectString, 1, 4, 3, 5);
+}
+
+
 void ODBCTest::testZeroRows()
 {
 	if (!_pSession) fail ("Test not available.");
@@ -842,6 +860,21 @@ void ODBCTest::testBLOBStmt()
 }
 
 
+void ODBCTest::testRecordSet()
+{
+	if (!_pSession) fail ("Test not available.");
+
+	for (int i = 0; i < 8;)
+	{
+		recreatePersonDateTimeTable();
+		_pSession->setFeature("autoBind", bindValue(i));
+		_pSession->setFeature("autoExtract", bindValue(i+1));
+		_pExecutor->recordSet();
+		i += 2;
+	}
+}
+
+
 void ODBCTest::testDateTime()
 {
 	if (!_pSession) fail ("Test not available.");
@@ -1368,7 +1401,7 @@ ODBCTest::SessionPtr ODBCTest::init(const std::string& driver,
 
 	try
 	{
-		std::cout << "Conecting to [" << dbConnString << ']' << std::endl;
+		std::cout << "Connecting to [" << dbConnString << ']' << std::endl;
 		SessionPtr ptr = new Session(Poco::Data::ODBC::Connector::KEY, dbConnString, 5);
 		if (!dbEncoding.empty())
 			ptr->setProperty("dbEncoding", dbEncoding);

+ 7 - 1
Data/ODBC/testsuite/src/ODBCTest.h

@@ -23,7 +23,7 @@
 #include "SQLExecutor.h"
 
 
-#define POCO_ODBC_TEST_DATABASE_SERVER "localhost"
+#define POCO_ODBC_TEST_DATABASE_SERVER "10.211.55.5"//"localhost"
 
 
 class ODBCTest: public CppUnit::TestCase
@@ -47,6 +47,10 @@ public:
 
 	virtual void testBareboneODBC() = 0;
 
+	virtual void testConnection();
+	virtual void testSession();
+	virtual void testSessionPool();
+
 	virtual void testZeroRows();
 	virtual void testSimpleAccess();
 	virtual void testComplexType();
@@ -107,6 +111,8 @@ public:
 	virtual void testBLOBContainer();
 	virtual void testBLOBStmt();
 
+	virtual void testRecordSet();
+
 	virtual void testDateTime();
 	virtual void testDate();
 	virtual void testTime();

+ 648 - 21
Data/ODBC/testsuite/src/SQLExecutor.cpp

@@ -29,6 +29,8 @@
 #include "Poco/Data/Date.h"
 #include "Poco/Data/Time.h"
 #include "Poco/Data/LOB.h"
+#include "Poco/Data/Session.h"
+#include "Poco/Data/SessionPool.h"
 #include "Poco/Data/StatementImpl.h"
 #include "Poco/Data/RecordSet.h"
 #include "Poco/Data/RowIterator.h"
@@ -54,6 +56,7 @@
 
 using namespace Poco::Data::Keywords;
 using Poco::Data::Session;
+using Poco::Data::SessionPool;
 using Poco::Data::Statement;
 using Poco::Data::RecordSet;
 using Poco::Data::Column;
@@ -73,6 +76,7 @@ using Poco::Data::ODBC::Preparator;
 using Poco::Data::ODBC::ConnectionException;
 using Poco::Data::ODBC::StatementException;
 using Poco::Data::ODBC::DataTruncatedException;
+using Poco::Data::ODBC::ConnectionError;
 using Poco::Data::ODBC::StatementError;
 using Poco::format;
 using Poco::Tuple;
@@ -354,13 +358,13 @@ void SQLExecutor::bareboneODBCTest(const std::string& dbConnString,
 		assertTrue (SQL_SUCCEEDED(rc) || SQL_NO_DATA == rc);
 
 		SQLULEN dateTimeColSize = 0;
-		SQLSMALLINT dateTimeDecDigits = 0;
+		SQLSMALLINT dateTimeDecDigits = -1;
 		if (SQL_SUCCEEDED(rc))
 		{
 			SQLLEN ind = 0;
 			rc = SQLGetData(hstmt, 3, SQL_C_SLONG, &dateTimeColSize, sizeof(SQLINTEGER), &ind);
 			poco_odbc_check_stmt (rc, hstmt);
-			rc = SQLGetData(hstmt, 14, SQL_C_SSHORT, &dateTimeDecDigits, sizeof(SQLSMALLINT), &ind);
+			rc = SQLGetData(hstmt, 15, SQL_C_SSHORT, &dateTimeDecDigits, sizeof(SQLSMALLINT), &ind);
 			poco_odbc_check_stmt (rc, hstmt);
 
 			assertTrue (sizeof(SQL_TIMESTAMP_STRUCT) <= dateTimeColSize);
@@ -484,17 +488,25 @@ void SQLExecutor::bareboneODBCTest(const std::string& dbConnString,
 				0);
 			poco_odbc_check_stmt (rc, hstmt);
 
-			SQLSMALLINT dataType = 0;
-			SQLULEN parameterSize = 0;
-			SQLSMALLINT decimalDigits = 0;
-			SQLSMALLINT nullable = 0;
-			rc = SQLDescribeParam(hstmt, 6, &dataType, &parameterSize, &decimalDigits, &nullable);
-			if (SQL_SUCCEEDED(rc))
+			if (dateTimeColSize == 0 || dateTimeDecDigits == -1)
 			{
-				if (parameterSize)
-					dateTimeColSize = parameterSize;
-				if (decimalDigits)
-					dateTimeDecDigits = decimalDigits;
+				SQLSMALLINT dataType = 0;
+				SQLULEN parameterSize = 0;
+				SQLSMALLINT decimalDigits = -1;
+				SQLSMALLINT nullable = 0;
+				rc = SQLDescribeParam(hstmt, 6, &dataType, &parameterSize, &decimalDigits, &nullable);
+				if (SQL_SUCCEEDED(rc))
+				{
+					if (parameterSize != 0 && dateTimeColSize == 0)
+						dateTimeColSize = parameterSize;
+					if (decimalDigits != -1 && dateTimeDecDigits == -1)
+						dateTimeDecDigits = decimalDigits;
+				}
+				else
+				{
+					std::cerr << '[' << name() << ']' << " Warning: could not get SQL_TYPE_TIMESTAMP "
+						"parameter description." << std::endl;
+				}
 			}
 			else
 				std::cerr << '[' << name() << ']' << " Warning: could not get SQL_TYPE_TIMESTAMP parameter description." << std::endl;
@@ -939,6 +951,191 @@ void SQLExecutor::bareboneODBCMultiResultTest(const std::string& dbConnString,
 }
 
 
+void SQLExecutor::bareboneODBCStoredFuncTest(const std::string& dbConnString,
+	const std::string& procCreateString,
+	const std::string& procExecuteString,
+	SQLExecutor::DataBinding bindMode,
+	SQLExecutor::DataExtraction extractMode)
+{
+	SQLRETURN rc;
+	SQLHENV henv = SQL_NULL_HENV;
+	SQLHDBC hdbc = SQL_NULL_HDBC;
+	SQLHSTMT hstmt = SQL_NULL_HSTMT;
+
+	// Environment begin
+	rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
+	poco_odbc_check_env(rc, henv);
+	rc = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0);
+	poco_odbc_check_env(rc, henv);
+
+	// Connection begin
+	rc = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
+	poco_odbc_check_dbc(rc, hdbc);
+
+	SQLCHAR connectOutput[1024] = { 0 };
+	SQLSMALLINT result;
+	rc = SQLDriverConnect(hdbc
+		, NULL
+		, (SQLCHAR*)dbConnString.c_str()
+		, (SQLSMALLINT)SQL_NTS
+		, connectOutput
+		, sizeof(connectOutput)
+		, &result
+		, SQL_DRIVER_NOPROMPT);
+	poco_odbc_check_dbc(rc, hdbc);
+
+	// retrieve datetime type information for this DBMS
+	rc = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
+	poco_odbc_check_stmt(rc, hstmt);
+
+	rc = SQLGetTypeInfo(hstmt, SQL_TYPE_TIMESTAMP);
+	poco_odbc_check_stmt(rc, hstmt);
+
+	rc = SQLFetch(hstmt);
+	assertTrue(SQL_SUCCEEDED(rc) || SQL_NO_DATA == rc);
+
+	SQLULEN dateTimeColSize = 0;
+	SQLSMALLINT dateTimeDecDigits = -1;
+	if (SQL_SUCCEEDED(rc))
+	{
+		SQLLEN ind = 0;
+		rc = SQLGetData(hstmt, 3, SQL_C_SLONG, &dateTimeColSize, sizeof(SQLINTEGER), &ind);
+		poco_odbc_check_stmt(rc, hstmt);
+		rc = SQLGetData(hstmt, 15, SQL_C_SSHORT, &dateTimeDecDigits, sizeof(SQLSMALLINT), &ind);
+		poco_odbc_check_stmt(rc, hstmt);
+
+		assertTrue(sizeof(SQL_TIMESTAMP_STRUCT) <= dateTimeColSize);
+	}
+	else if (SQL_NO_DATA == rc)
+		std::cerr << '[' << name() << ']' << " Warning: no SQL_TYPE_TIMESTAMP data type info returned by driver." << std::endl;
+
+	rc = SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
+	poco_odbc_check_stmt(rc, hstmt);
+
+		// Statement begin
+		rc = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
+		poco_odbc_check_stmt(rc, hstmt);
+
+		std::string sql = "DROP PROCEDURE TestStoredProcedure";
+		SQLCHAR* pStr = (SQLCHAR*)sql.c_str();
+		SQLExecDirect(hstmt, pStr, (SQLINTEGER)sql.length());
+		//no return code check - ignore drop errors
+
+		// create stored prcedure and go
+		sql = procCreateString;
+		pStr = (SQLCHAR*)sql.c_str();
+		rc = SQLPrepare(hstmt, pStr, (SQLINTEGER)sql.length());
+		poco_odbc_check_stmt(rc, hstmt);
+
+		rc = SQLExecute(hstmt);
+		poco_odbc_check_stmt(rc, hstmt);
+
+		char inParam[10] = "1234";
+		char outParam[10] = "";
+		char retVal[10] = "";
+
+		sql = procExecuteString;
+		pStr = (SQLCHAR*)sql.c_str();
+		rc = SQLPrepare(hstmt, pStr, (SQLINTEGER)sql.length());
+		poco_odbc_check_stmt(rc, hstmt);
+
+		SQLLEN li[3] = { SQL_NTS, SQL_NTS, SQL_NTS };
+		SQLINTEGER size = (SQLINTEGER)strlen(inParam);
+
+		if (SQLExecutor::PB_AT_EXEC == bindMode)
+			li[0] = SQL_LEN_DATA_AT_EXEC(size);
+
+		rc = SQLBindParameter(hstmt,
+			(SQLUSMALLINT)1,
+			SQL_PARAM_OUTPUT,
+			SQL_C_CHAR,
+			SQL_VARCHAR,
+			(SQLUINTEGER)sizeof(retVal),
+			0,
+			(SQLPOINTER)retVal,
+			sizeof(retVal),
+			&li[0]);
+		poco_odbc_check_stmt(rc, hstmt);
+
+		if (SQLExecutor::PB_AT_EXEC == bindMode)
+			li[1] = SQL_LEN_DATA_AT_EXEC(size);
+
+		rc = SQLBindParameter(hstmt,
+			(SQLUSMALLINT)2,
+			SQL_PARAM_INPUT,
+			SQL_C_CHAR,
+			SQL_VARCHAR,
+			(SQLUINTEGER)sizeof(inParam),
+			0,
+			(SQLPOINTER)inParam,
+			sizeof(inParam),
+			&li[1]);
+		poco_odbc_check_stmt(rc, hstmt);
+
+		if (SQLExecutor::PB_AT_EXEC == bindMode)
+			li[2] = SQL_LEN_DATA_AT_EXEC(size);
+
+		rc = SQLBindParameter(hstmt,
+			(SQLUSMALLINT)3,
+			SQL_PARAM_OUTPUT,
+			SQL_C_CHAR,
+			SQL_VARCHAR,
+			(SQLUINTEGER)sizeof(outParam),
+			0,
+			(SQLPOINTER)outParam,
+			sizeof(outParam),
+			&li[2]);
+		poco_odbc_check_stmt(rc, hstmt);
+
+		rc = SQLExecute(hstmt);
+		if (rc && SQL_NEED_DATA != rc)
+		{
+			std::cout << "rc=" << rc << ", SQL_NEED_DATA=" << SQL_NEED_DATA << '\n';
+			poco_odbc_check_stmt(rc, hstmt);
+		}
+		assertTrue(SQL_NEED_DATA == rc || SQL_SUCCEEDED(rc));
+
+		if (SQL_NEED_DATA == rc)
+		{
+			SQLPOINTER pParam = 0;
+			while (SQL_NEED_DATA == (rc = SQLParamData(hstmt, &pParam)))
+			{
+				if ((pParam != (SQLPOINTER)retVal) &&
+					(pParam != (SQLPOINTER)inParam) &&
+					(pParam != (SQLPOINTER)outParam))
+				{
+					fail("Parameter mismatch.");
+				}
+
+				assertTrue(0 != (SQLINTEGER)size);
+				rc = SQLPutData(hstmt, pParam, (SQLINTEGER)size);
+				poco_odbc_check_stmt(rc, hstmt);
+			}
+		}
+		poco_odbc_check_stmt(rc, hstmt);
+		assertTrue(std::string(outParam) == std::string(inParam));
+		assertTrue(std::string(retVal) == std::string(inParam));
+
+		sql = "DROP PROCEDURE TestStoredProcedure";
+		pStr = (SQLCHAR*)sql.c_str();
+		rc = SQLExecDirect(hstmt, pStr, (SQLINTEGER)sql.length());
+		poco_odbc_check_stmt(rc, hstmt);
+
+		rc = SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
+		poco_odbc_check_stmt(rc, hstmt);
+
+	// Connection end
+	rc = SQLDisconnect(hdbc);
+	poco_odbc_check_dbc(rc, hdbc);
+	rc = SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
+	poco_odbc_check_dbc(rc, hdbc);
+
+	// Environment end
+	rc = SQLFreeHandle(SQL_HANDLE_ENV, henv);
+	poco_odbc_check_env(rc, henv);
+}
+
+
 void SQLExecutor::execute(const std::string& sql)
 {
 	try { session() << sql, now;  }
@@ -947,6 +1144,314 @@ void SQLExecutor::execute(const std::string& sql)
 }
 
 
+void SQLExecutor::connection(const std::string& connectString)
+{
+	Poco::Data::ODBC::Connection c;
+	assertFalse (c.isConnected());
+	assertTrue (c.connect(connectString, 5));
+	assertTrue (c.isConnected());
+	assertTrue (c.getTimeout() == 5);
+	c.setTimeout(6);
+	assertTrue (c.getTimeout() == 6);
+	assertTrue (c.disconnect());
+	assertFalse (c.isConnected());
+	assertTrue (c.connect(connectString));
+	assertTrue (c.isConnected());
+	try
+	{
+		c.connect();
+		fail ("Connection attempt must fail when already connected");
+	}
+	catch(const Poco::InvalidAccessException&){}
+	assertTrue (c.disconnect());
+	assertFalse (c.isConnected());
+	try
+	{
+		c.connect();
+		fail ("Connection attempt with empty string must fail");
+	}
+	catch(const Poco::InvalidArgumentException&){}
+}
+
+
+void SQLExecutor::session(const std::string& connectString, int timeout)
+{
+	Poco::Data::Session s(Poco::Data::ODBC::Connector::KEY, connectString, timeout);
+	assertTrue (s.isConnected());
+	s.close();
+	assertTrue (!s.isConnected());
+	s.open();
+	assertTrue (s.isConnected());
+	s.reconnect();
+	assertTrue (s.isConnected());
+	s.close();
+	assertTrue (!s.isConnected());
+	s.reconnect();
+	assertTrue (s.isConnected());
+
+	Poco::Any any = s.getProperty("handle");
+	assertTrue (typeid(SQLHDBC) == any.type());
+	SQLHDBC hdbc = Poco::AnyCast<SQLHDBC>(any);
+	assertTrue (SQL_NULL_HDBC != hdbc);
+	SQLRETURN rc = SQLDisconnect(hdbc);
+	assertTrue (!Utility::isError(rc));
+	assertTrue (!s.isConnected());
+	s.open();
+	assertTrue (s.isConnected());
+
+	hdbc = Poco::AnyCast<SQLHDBC>(any);
+	rc = SQLDisconnect(hdbc);
+	assertTrue (!Utility::isError(rc));
+	assertTrue (!s.isConnected());
+	s.reconnect();
+	assertTrue (s.isConnected());
+	s.close();
+	assertTrue (!s.isConnected());
+	s.reconnect();
+	assertTrue (s.isConnected());
+	s.reconnect();
+	assertTrue (s.isConnected());
+}
+
+
+void SQLExecutor::sessionPool(const std::string& connectString, int minSessions, int maxSessions, int idleTime, int timeout)
+{
+	assertTrue (minSessions <= maxSessions);
+
+	SessionPool sp("ODBC", connectString, minSessions, maxSessions, idleTime, timeout);
+	assertEqual (0, sp.allocated());
+	assertEqual (maxSessions, sp.available());
+	Session s1 = sp.get();
+	assertEqual (minSessions, sp.allocated());
+	assertEqual (maxSessions-1, sp.available());
+	s1 = sp.get();
+	assertEqual (2, sp.allocated());
+	assertEqual (maxSessions-1, sp.available());
+	{
+		Session s2 = sp.get();
+		assertEqual (2, sp.allocated());
+		assertEqual (maxSessions-2, sp.available());
+	}
+	assertEqual (2, sp.allocated());
+	assertEqual (maxSessions-1, sp.available());
+
+	Thread::sleep(idleTime + 500);
+	assertEqual (maxSessions-minSessions, sp.available());
+
+	try
+	{
+		sp.setFeature("autoBind", true);
+		fail("SessionPool must throw on setFeature after the first session was created.");
+	}
+	catch(const Poco::InvalidAccessException&) {}
+	try
+	{
+		sp.setProperty("handle", SQL_NULL_HDBC);
+		fail("SessionPool must throw on setProperty after the first session was created.");
+	}
+	catch(const Poco::InvalidAccessException&) {}
+
+	std::vector<Session> sessions;
+	for (int i = 0; i < maxSessions-minSessions; ++i)
+	{
+		sessions.push_back(sp.get());
+	}
+
+	try
+	{
+		Session s = sp.get();
+		fail("SessionPool must throw when no sesions available.");
+	}
+	catch(const Poco::Data::SessionPoolExhaustedException&) {}
+
+	sp.shutdown();
+	try
+	{
+		Session s = sp.get();
+		fail("SessionPool that was shut down must throw on get.");
+	}
+	catch(const Poco::InvalidAccessException&) {}
+
+	{
+		SessionPool pool("ODBC", connectString, 1, 4, 2, 10);
+
+		pool.setFeature("f1", true);
+		assertTrue (pool.getFeature("f1"));
+		try { pool.getFeature("g1"); fail ("must fail"); }
+		catch ( Poco::NotFoundException& ) { }
+
+		pool.setProperty("p1", 1);
+		assertTrue (1 == Poco::AnyCast<int>(pool.getProperty("p1")));
+		try { pool.getProperty("r1"); fail ("must fail"); }
+		catch ( Poco::NotFoundException& ) { }
+
+		assertTrue (pool.capacity() == 4);
+		assertTrue (pool.allocated() == 0);
+		assertTrue (pool.idle() == 0);
+		assertTrue (pool.connTimeout() == 10);
+		assertTrue (pool.available() == 4);
+		assertTrue (pool.dead() == 0);
+		assertTrue (pool.allocated() == pool.used() + pool.idle());
+		Session s1(pool.get());
+
+		assertTrue (s1.getFeature("f1"));
+		assertTrue (1 == Poco::AnyCast<int>(s1.getProperty("p1")));
+
+		try { pool.setFeature("f1", true); fail ("must fail"); }
+		catch ( Poco::InvalidAccessException& ) { }
+
+		try { pool.setProperty("p1", 1); fail ("must fail"); }
+		catch ( Poco::InvalidAccessException& ) { }
+
+		assertTrue (pool.capacity() == 4);
+		assertTrue (pool.allocated() == 1);
+		assertTrue (pool.idle() == 0);
+		assertTrue (pool.connTimeout() == 10);
+		assertTrue (pool.available() == 3);
+		assertTrue (pool.dead() == 0);
+		assertTrue (pool.allocated() == pool.used() + pool.idle());
+
+		Session s2(pool.get("f1", false));
+		assertTrue (!s2.getFeature("f1"));
+		assertTrue (1 == Poco::AnyCast<int>(s2.getProperty("p1")));
+
+		assertTrue (pool.capacity() == 4);
+		assertTrue (pool.allocated() == 2);
+		assertTrue (pool.idle() == 0);
+		assertTrue (pool.connTimeout() == 10);
+		assertTrue (pool.available() == 2);
+		assertTrue (pool.dead() == 0);
+		assertTrue (pool.allocated() == pool.used() + pool.idle());
+
+		{
+			Session s3(pool.get("p1", 2));
+			assertTrue (s3.getFeature("f1"));
+			assertTrue (2 == Poco::AnyCast<int>(s3.getProperty("p1")));
+
+			assertTrue (pool.capacity() == 4);
+			assertTrue (pool.allocated() == 3);
+			assertTrue (pool.idle() == 0);
+			assertTrue (pool.connTimeout() == 10);
+			assertTrue (pool.available() == 1);
+			assertTrue (pool.dead() == 0);
+			assertTrue (pool.allocated() == pool.used() + pool.idle());
+		}
+
+		assertTrue (pool.capacity() == 4);
+		assertTrue (pool.allocated() == 3);
+		assertTrue (pool.idle() == 1);
+		assertTrue (pool.connTimeout() == 10);
+		assertTrue (pool.available() == 2);
+		assertTrue (pool.dead() == 0);
+		assertTrue (pool.allocated() == pool.used() + pool.idle());
+
+		Session s4(pool.get());
+		assertTrue (s4.getFeature("f1"));
+		assertTrue (1 == Poco::AnyCast<int>(s4.getProperty("p1")));
+
+		assertTrue (pool.capacity() == 4);
+		assertTrue (pool.allocated() == 3);
+		assertTrue (pool.idle() == 0);
+		assertTrue (pool.connTimeout() == 10);
+		assertTrue (pool.available() == 1);
+		assertTrue (pool.dead() == 0);
+		assertTrue (pool.allocated() == pool.used() + pool.idle());
+
+		Session s5(pool.get());
+
+		assertTrue (pool.capacity() == 4);
+		assertTrue (pool.allocated() == 4);
+		assertTrue (pool.idle() == 0);
+		assertTrue (pool.connTimeout() == 10);
+		assertTrue (pool.available() == 0);
+		assertTrue (pool.dead() == 0);
+		assertTrue (pool.allocated() == pool.used() + pool.idle());
+
+		try
+		{
+			Session s6(pool.get());
+			fail("pool exhausted - must throw");
+		}
+		catch (Poco::Data::SessionPoolExhaustedException&) { }
+
+		s5.close();
+		assertTrue (pool.capacity() == 4);
+		assertTrue (pool.allocated() == 4);
+		assertTrue (pool.idle() == 1);
+		assertTrue (pool.connTimeout() == 10);
+		assertTrue (pool.available() == 1);
+		assertTrue (pool.dead() == 0);
+		assertTrue (pool.allocated() == pool.used() + pool.idle());
+
+		try
+		{
+			s5 << "DROP TABLE IF EXISTS Test", now;
+			fail("session unusable - must throw");
+		}
+		catch (Poco::Data::SessionUnavailableException&) { }
+
+		s4.close();
+		assertTrue (pool.capacity() == 4);
+		assertTrue (pool.allocated() == 4);
+		assertTrue (pool.idle() == 2);
+		assertTrue (pool.connTimeout() == 10);
+		assertTrue (pool.available() == 2);
+		assertTrue (pool.dead() == 0);
+		assertTrue (pool.allocated() == pool.used() + pool.idle());
+
+		Thread::sleep(5000); // time to clean up idle sessions
+
+		assertTrue (pool.capacity() == 4);
+		assertTrue (pool.allocated() == 2);
+		assertTrue (pool.idle() == 0);
+		assertTrue (pool.connTimeout() == 10);
+		assertTrue (pool.available() == 2);
+		assertTrue (pool.dead() == 0);
+		assertTrue (pool.allocated() == pool.used() + pool.idle());
+
+		Session s6(pool.get());
+
+		assertTrue (pool.capacity() == 4);
+		assertTrue (pool.allocated() == 3);
+		assertTrue (pool.idle() == 0);
+		assertTrue (pool.connTimeout() == 10);
+		assertTrue (pool.available() == 1);
+		assertTrue (pool.dead() == 0);
+		assertTrue (pool.allocated() == pool.used() + pool.idle());
+
+		s6.setFeature("connected", false);
+		assertTrue (pool.dead() == 1);
+
+		s6.close();
+		assertTrue (pool.capacity() == 4);
+		assertTrue (pool.allocated() == 2);
+		assertTrue (pool.idle() == 0);
+		assertTrue (pool.connTimeout() == 10);
+		assertTrue (pool.available() == 2);
+		assertTrue (pool.dead() == 0);
+		assertTrue (pool.allocated() == pool.used() + pool.idle());
+
+		assertTrue (pool.isActive());
+		pool.shutdown();
+		assertTrue (!pool.isActive());
+		try
+		{
+			Session s7(pool.get());
+			fail("pool shut down - must throw");
+		}
+		catch (InvalidAccessException&) { }
+
+		assertTrue (pool.capacity() == 4);
+		assertTrue (pool.allocated() == 0);
+		assertTrue (pool.idle() == 0);
+		assertTrue (pool.connTimeout() == 10);
+		assertTrue (pool.available() == 0);
+		assertTrue (pool.dead() == 0);
+		assertTrue (pool.allocated() == pool.used() + pool.idle());
+	}
+}
+
+
 void SQLExecutor::zeroRows()
 {
 	Statement stmt = (session() << "SELECT * FROM Person WHERE 0 = 1");
@@ -2470,6 +2975,106 @@ void SQLExecutor::blobStmt()
 }
 
 
+void SQLExecutor::recordSet()
+{
+	std::string funct = "dateTime()";
+
+	std::string lastName("lastname");
+	std::string firstName("firstname");
+	std::string address("Address");
+	DateTime born(1965, 6, 18, 5, 35, 1);
+	DateTime born2(1991, 6, 18, 14, 35, 1);
+
+	try
+	{
+		{
+			Statement stmt = (session() << "SELECT COUNT(*) AS row_count FROM Person", now);
+			RecordSet rset(stmt);
+			assertTrue (rset.rowCount() == 1);
+			assertTrue (rset["row_count"].convert<int>() == 0);
+		}
+
+		{
+			Statement stmt = (session() <<
+				"INSERT INTO Person VALUES (?,?,?,?)",
+				use(lastName), use(firstName), use(address), use(born), now);
+			RecordSet rset(stmt);
+			assertTrue (rset.rowCount() == 0);
+			assertTrue (rset.affectedRowCount() == 1);
+		}
+
+		{
+			Statement stmt = (session() << "SELECT COUNT(*) AS row_count FROM Person", now);
+			RecordSet rset(stmt);
+			assertTrue (rset.rowCount() == 1);
+			assertTrue (rset["row_count"].convert<int>() == 1);
+		}
+
+		{
+			Statement stmt = (session() << "SELECT Born FROM Person", now);
+			RecordSet rset(stmt);
+			assertTrue (rset.rowCount() == 1);
+			assertTrue (rset["Born"].convert<DateTime>() == born);
+		}
+
+		{
+			Statement stmt = (session() <<
+				"DELETE FROM Person WHERE born = ?", use(born), now);
+			RecordSet rset(stmt);
+			assertTrue (rset.rowCount() == 0);
+			assertTrue (rset.affectedRowCount() == 1);
+		}
+
+		{
+			Statement stmt = (session() <<
+				"INSERT INTO Person VALUES (?,?,?,?)",
+				use(lastName), use(firstName), use(address), use(born), now);
+			RecordSet rset(stmt);
+			assertTrue (rset.rowCount() == 0);
+			assertTrue (rset.affectedRowCount() == 1);
+		}
+
+		{
+			Statement stmt = (session() <<
+				"INSERT INTO Person VALUES (?,?,?,?)",
+				use(lastName), use(firstName), use(address), use(born2), now);
+			RecordSet rset(stmt);
+			assertTrue (rset.rowCount() == 0);
+			assertTrue (rset.affectedRowCount() == 1);
+		}
+
+		{
+			Statement stmt = (session() << "SELECT COUNT(*) AS row_count FROM Person", now);
+			RecordSet rset(stmt);
+			assertTrue (rset.rowCount() == 1);
+			assertTrue (rset["row_count"].convert<int>() == 2);
+		}
+
+		{
+			Statement stmt = (session() << "SELECT Born FROM Person ORDER BY Born DESC", now);
+			RecordSet rset(stmt);
+			assertTrue (rset.rowCount() == 2);
+			assertTrue (rset["Born"].convert<DateTime>() == born2);
+			rset.moveNext();
+			assertTrue (rset["Born"].convert<DateTime>() == born);
+			rset.moveFirst();
+			assertTrue (rset["Born"].convert<DateTime>() == born2);
+			rset.moveLast();
+			assertTrue (rset["Born"].convert<DateTime>() == born);
+		}
+
+		{
+			Statement stmt = (session() << "DELETE FROM Person", now);
+			RecordSet rset(stmt);
+			assertTrue (rset.rowCount() == 0);
+			assertTrue (rset.affectedRowCount() == 2);
+		}
+	}
+	catch (ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail(funct); }
+	catch (StatementException& se){ std::cout << se.toString() << std::endl; fail(funct); }
+}
+
+
 void SQLExecutor::dateTime()
 {
 	std::string funct = "dateTime()";
@@ -3457,22 +4062,35 @@ void SQLExecutor::sqlChannel(const std::string& connect)
 	try
 	{
 		AutoPtr<SQLChannel> pChannel = new SQLChannel(Poco::Data::ODBC::Connector::KEY, connect, "TestSQLChannel");
+		Stopwatch sw; sw.start();
+		while (!pChannel->isRunning())
+		{
+			Thread::sleep(10);
+			if (sw.elapsedSeconds() > 3)
+				fail ("SQLExecutor::sqlLogger(): SQLChannel timed out");
+		}
+
+		pChannel->setProperty("bulk", "true");
 		pChannel->setProperty("keep", "2 seconds");
 
 		Message msgInf("InformationSource", "a Informational async message", Message::PRIO_INFORMATION);
 		pChannel->log(msgInf);
+		while (pChannel->logged() != 1) Thread::sleep(10);
+
 		Message msgWarn("WarningSource", "b Warning async message", Message::PRIO_WARNING);
 		pChannel->log(msgWarn);
-		pChannel->wait();
+		while (pChannel->logged() != 2) Thread::sleep(10);
 
-		pChannel->setProperty("async", "false");
 		Message msgInfS("InformationSource", "c Informational sync message", Message::PRIO_INFORMATION);
 		pChannel->log(msgInfS);
+		while (pChannel->logged() != 3) Thread::sleep(10);
 		Message msgWarnS("WarningSource", "d Warning sync message", Message::PRIO_WARNING);
 		pChannel->log(msgWarnS);
+		while (pChannel->logged() != 4) Thread::sleep(10);
 
 		RecordSet rs(session(), "SELECT * FROM T_POCO_LOG ORDER by Text");
-		assertTrue (4 == rs.rowCount());
+		size_t rc = rs.rowCount();
+		assertTrue (4 == rc);
 		assertTrue ("InformationSource" == rs["Source"]);
 		assertTrue ("a Informational async message" == rs["Text"]);
 		rs.moveNext();
@@ -3485,12 +4103,14 @@ void SQLExecutor::sqlChannel(const std::string& connect)
 		assertTrue ("WarningSource" == rs["Source"]);
 		assertTrue ("d Warning sync message" == rs["Text"]);
 
-		Thread::sleep(3000);
+		Thread::sleep(3000); // give it time to archive
 
 		Message msgInfA("InformationSource", "e Informational sync message", Message::PRIO_INFORMATION);
 		pChannel->log(msgInfA);
+		while (pChannel->logged() != 5) Thread::sleep(10);
 		Message msgWarnA("WarningSource", "f Warning sync message", Message::PRIO_WARNING);
 		pChannel->log(msgWarnA);
+		while (pChannel->logged() != 6) Thread::sleep(10);
 
 		RecordSet rs1(session(), "SELECT * FROM T_POCO_LOG_ARCHIVE");
 		assertTrue (4 == rs1.rowCount());
@@ -3504,7 +4124,6 @@ void SQLExecutor::sqlChannel(const std::string& connect)
 		rs2.moveNext();
 		assertTrue ("WarningSource" == rs2["Source"]);
 		assertTrue ("f Warning sync message" == rs2["Text"]);
-
 	}
 	catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail ("sqlChannel()"); }
 	catch(StatementException& se){ std::cout << se.toString() << std::endl; fail ("sqlChannel()"); }
@@ -3516,14 +4135,23 @@ void SQLExecutor::sqlLogger(const std::string& connect)
 	try
 	{
 		Logger& root = Logger::root();
-		root.setChannel(new SQLChannel(Poco::Data::ODBC::Connector::KEY, connect, "TestSQLChannel"));
+		SQLChannel* pSQLChannel = new SQLChannel(Poco::Data::ODBC::Connector::KEY, connect, "TestSQLChannel");
+		Stopwatch sw; sw.start();
+		while (!pSQLChannel->isRunning())
+		{
+			Thread::sleep(10);
+			if (sw.elapsedSeconds() > 3)
+				fail ("SQLExecutor::sqlLogger(): SQLChannel timed out");
+		}
+
+		root.setChannel(pSQLChannel);
 		root.setLevel(Message::PRIO_INFORMATION);
 
 		root.information("a Informational message");
 		root.warning("b Warning message");
 		root.debug("Debug message");
 
-		Thread::sleep(100);
+		while (pSQLChannel->logged() != 2) Thread::sleep(100);
 		RecordSet rs(session(), "SELECT * FROM T_POCO_LOG ORDER by Text");
 		assertTrue (2 == rs.rowCount());
 		assertTrue ("TestSQLChannel" == rs["Source"]);
@@ -3531,7 +4159,6 @@ void SQLExecutor::sqlLogger(const std::string& connect)
 		rs.moveNext();
 		assertTrue ("TestSQLChannel" == rs["Source"]);
 		assertTrue ("b Warning message" == rs["Text"]);
-		root.setChannel(nullptr);
 	}
 	catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail ("sqlLogger()"); }
 	catch(StatementException& se){ std::cout << se.toString() << std::endl; fail ("sqlLogger()"); }
@@ -3852,7 +4479,7 @@ struct TestRollbackTransactor
 
 void SQLExecutor::transactor()
 {
-	std::string funct = "transaction()";
+	std::string funct = "transactor()";
 	int count = 0;
 
 	bool autoCommit = session().getFeature("autoCommit");

+ 15 - 3
Data/ODBC/testsuite/src/SQLExecutor.h

@@ -48,7 +48,7 @@
 	if (!SQL_SUCCEEDED(r))	\
 	{ \
 		Poco::Data::ODBC::StatementException se(h); \
-		std::cout << se.toString() << std::endl; \
+		std::cout << se.displayText() << std::endl; \
 	} \
 	assert (SQL_SUCCEEDED(r))
 
@@ -57,7 +57,7 @@
 	if (!SQL_SUCCEEDED(r))	\
 	{ \
 		Poco::Data::ODBC::DescriptorException de(h); \
-		std::cout << de.toString() << std::endl; \
+		std::cout << de.displayText() << std::endl; \
 	} \
 	assert (SQL_SUCCEEDED(r))
 
@@ -106,13 +106,24 @@ public:
 		SQLExecutor::DataExtraction extractMode,
 		const std::string& insert = MULTI_INSERT,
 		const std::string& select = MULTI_SELECT);
-		/// The above two functions use "bare bone" ODBC API calls
+
+	void bareboneODBCStoredFuncTest(const std::string& dbConnString,
+		const std::string& tableCreateString,
+		const std::string& procExecuteString,
+		SQLExecutor::DataBinding bindMode,
+		SQLExecutor::DataExtraction extractMode);
+		/// The above three functions use "bare bone" ODBC API calls
 		/// (i.e. calls are not "wrapped" in PocoData framework structures).
 		/// The purpose of the functions is to verify that a driver behaves
 		/// correctly as well as to determine its capabilities
 		/// (e.g. SQLGetData() restrictions relaxation policy, if any).
 		/// If these test pass, subsequent tests failures are likely ours.
 
+	void connection(const std::string& connectString);
+	void session(const std::string& connectString, int timeout);
+	void sessionPool(const std::string& connectString,
+		int minSessions, int maxSessions, int idleTime, int timeout);
+
 	void zeroRows();
 	void simpleAccess();
 	void complexType();
@@ -463,6 +474,7 @@ public:
 	}
 
 	void blobStmt();
+	void recordSet();
 
 	void dateTime();
 	void date();

+ 0 - 5
Data/SQLite/src/SessionImpl.cpp

@@ -257,11 +257,6 @@ Poco::Any SessionImpl::getTransactionType(const std::string& prop) const
 
 void SessionImpl::autoCommit(const std::string&, bool)
 {
-	// The problem here is to decide whether to call commit or rollback
-	// when autocommit is set to true. Hence, it is best not to implement
-	// this explicit call and only implicitly support autocommit setting.
-	throw NotImplementedException(
-		"SQLite autocommit is implicit with begin/commit/rollback.");
 }
 
 

+ 1 - 0
Data/SQLite/src/Utility.cpp

@@ -138,6 +138,7 @@ Utility::Utility()
 		_types.insert(TypeMap::value_type("TIMESTAMP", MetaColumn::FDT_TIMESTAMP));
 		_types.insert(TypeMap::value_type("UUID", MetaColumn::FDT_UUID));
 		_types.insert(TypeMap::value_type("GUID", MetaColumn::FDT_UUID));
+		_types.insert(TypeMap::value_type("JSON", MetaColumn::FDT_JSON));
 	}
 }
 

+ 80 - 45
Data/SQLite/testsuite/src/SQLiteTest.cpp

@@ -89,6 +89,7 @@ using Poco::Int64;
 using Poco::Dynamic::Var;
 using Poco::Data::SQLite::Utility;
 using Poco::delegate;
+using Poco::Stopwatch;
 
 
 class Person
@@ -1408,7 +1409,7 @@ void SQLiteTest::testNonexistingDB()
 		Session tmp (Poco::Data::SQLite::Connector::KEY, "foo/bar/nonexisting.db", 1);
 		fail("non-existing DB must throw");
 	}
-	catch(ConnectionFailedException& ex)
+	catch(ConnectionFailedException&)
 	{
 		return;
 	}
@@ -2474,53 +2475,68 @@ void SQLiteTest::testSQLChannel()
 		"DateTime DATE)", now;
 
 	AutoPtr<SQLChannel> pChannel = new SQLChannel(Poco::Data::SQLite::Connector::KEY, "dummy.db", "TestSQLChannel");
+	Stopwatch sw; sw.start();
+	while (!pChannel->isRunning())
+	{
+		Thread::sleep(10);
+		if (sw.elapsedSeconds() > 3)
+			fail ("SQLExecutor::sqlLogger(): SQLChannel timed out");
+	}
+	// bulk binding mode is not suported by SQLite, but SQLChannel should handle it internally
+	pChannel->setProperty("bulk", "true");
 	pChannel->setProperty("keep", "2 seconds");
 
 	Message msgInf("InformationSource", "a Informational async message", Message::PRIO_INFORMATION);
 	pChannel->log(msgInf);
+	while (pChannel->logged() != 1) Thread::sleep(100);
+
 	Message msgWarn("WarningSource", "b Warning async message", Message::PRIO_WARNING);
 	pChannel->log(msgWarn);
-	pChannel->wait();
+	while (pChannel->logged() != 2) Thread::sleep(10);
 
-	pChannel->setProperty("async", "false");
 	Message msgInfS("InformationSource", "c Informational sync message", Message::PRIO_INFORMATION);
 	pChannel->log(msgInfS);
+	while (pChannel->logged() != 3) Thread::sleep(10);
 	Message msgWarnS("WarningSource", "d Warning sync message", Message::PRIO_WARNING);
 	pChannel->log(msgWarnS);
+	while (pChannel->logged() != 4) Thread::sleep(10);
 
 	RecordSet rs(tmp, "SELECT * FROM T_POCO_LOG ORDER by Text");
-	assertTrue (4 == rs.rowCount());
-	assertTrue ("InformationSource" == rs["Source"]);
-	assertTrue ("a Informational async message" == rs["Text"]);
+	size_t rc = rs.rowCount();
+	assertTrue(4 == rc);
+	assertTrue("InformationSource" == rs["Source"]);
+	assertTrue("a Informational async message" == rs["Text"]);
 	rs.moveNext();
-	assertTrue ("WarningSource" == rs["Source"]);
-	assertTrue ("b Warning async message" == rs["Text"]);
+	assertTrue("WarningSource" == rs["Source"]);
+	assertTrue("b Warning async message" == rs["Text"]);
 	rs.moveNext();
-	assertTrue ("InformationSource" == rs["Source"]);
-	assertTrue ("c Informational sync message" == rs["Text"]);
+	assertTrue("InformationSource" == rs["Source"]);
+	assertTrue("c Informational sync message" == rs["Text"]);
 	rs.moveNext();
-	assertTrue ("WarningSource" == rs["Source"]);
-	assertTrue ("d Warning sync message" == rs["Text"]);
+	assertTrue("WarningSource" == rs["Source"]);
+	assertTrue("d Warning sync message" == rs["Text"]);
 
-	Thread::sleep(3000);
+	Thread::sleep(3000); // give it time to archive
 
 	Message msgInfA("InformationSource", "e Informational sync message", Message::PRIO_INFORMATION);
 	pChannel->log(msgInfA);
+	while (pChannel->logged() != 5) Thread::sleep(10);
 	Message msgWarnA("WarningSource", "f Warning sync message", Message::PRIO_WARNING);
 	pChannel->log(msgWarnA);
+	while (pChannel->logged() != 6) Thread::sleep(10);
 
 	RecordSet rs1(tmp, "SELECT * FROM T_POCO_LOG_ARCHIVE");
-	assertTrue (4 == rs1.rowCount());
+	assertTrue(4 == rs1.rowCount());
 
 	pChannel->setProperty("keep", "");
-	assertTrue ("forever" == pChannel->getProperty("keep"));
+	assertTrue("forever" == pChannel->getProperty("keep"));
 	RecordSet rs2(tmp, "SELECT * FROM T_POCO_LOG ORDER by Text");
-	assertTrue (2 == rs2.rowCount());
-	assertTrue ("InformationSource" == rs2["Source"]);
-	assertTrue ("e Informational sync message" == rs2["Text"]);
+	assertTrue(2 == rs2.rowCount());
+	assertTrue("InformationSource" == rs2["Source"]);
+	assertTrue("e Informational sync message" == rs2["Text"]);
 	rs2.moveNext();
-	assertTrue ("WarningSource" == rs2["Source"]);
-	assertTrue ("f Warning sync message" == rs2["Text"]);
+	assertTrue("WarningSource" == rs2["Source"]);
+	assertTrue("f Warning sync message" == rs2["Text"]);
 }
 
 
@@ -2537,25 +2553,30 @@ void SQLiteTest::testSQLLogger()
 		"Text VARCHAR,"
 		"DateTime DATE)", now;
 
+	Logger& root = Logger::root();
+	AutoPtr<SQLChannel> pSQLChannel = new SQLChannel(Poco::Data::SQLite::Connector::KEY, "dummy.db", "TestSQLChannel");
+	Stopwatch sw; sw.start();
+	while (!pSQLChannel->isRunning())
 	{
-		AutoPtr<SQLChannel> pChannel = new SQLChannel(Poco::Data::SQLite::Connector::KEY, "dummy.db", "TestSQLChannel");
-		Logger& root = Logger::root();
-		root.setChannel(pChannel);
-		root.setLevel(Message::PRIO_INFORMATION);
-
-		root.information("Informational message");
-		root.warning("Warning message");
-		root.debug("Debug message");
+		Thread::sleep(10);
+		if (sw.elapsedSeconds() > 3)
+			fail ("SQLExecutor::sqlLogger(): SQLChannel timed out");
 	}
+	root.setChannel(pSQLChannel);
+	root.setLevel(Message::PRIO_INFORMATION);
+
+	root.information("a Informational message");
+	root.warning("b Warning message");
+	root.debug("Debug message");
 
-	Thread::sleep(100);
-	RecordSet rs(tmp, "SELECT * FROM T_POCO_LOG ORDER by DateTime");
-	assertTrue (2 == rs.rowCount());
-	assertTrue ("TestSQLChannel" == rs["Source"]);
-	assertTrue ("Informational message" == rs["Text"]);
+	while (pSQLChannel->logged() != 2) Thread::sleep(100);
+	RecordSet rs(tmp, "SELECT * FROM T_POCO_LOG ORDER by Text");
+	assertTrue(2 == rs.rowCount());
+	assertTrue("TestSQLChannel" == rs["Source"]);
+	assertTrue("a Informational message" == rs["Text"]);
 	rs.moveNext();
-	assertTrue ("TestSQLChannel" == rs["Source"]);
-	assertTrue ("Warning message" == rs["Text"]);
+	assertTrue("TestSQLChannel" == rs["Source"]);
+	assertTrue("b Warning message" == rs["Text"]);
 }
 
 
@@ -3130,12 +3151,6 @@ void SQLiteTest::testSessionTransaction()
 	Session local (Poco::Data::SQLite::Connector::KEY, "dummy.db");
 	assertTrue (local.isConnected());
 
-	try
-	{
-		local.setFeature("autoCommit", true);
-		fail ("Setting SQLite auto-commit explicitly must fail!");
-	}
-	catch (NotImplementedException&) { }
 	assertTrue (local.getFeature("autoCommit"));
 
 	std::string funct = "transaction()";
@@ -3235,12 +3250,16 @@ void SQLiteTest::testTransaction()
 	std::string tableName("Person");
 	lastNames.push_back("LN1");
 	lastNames.push_back("LN2");
+	lastNames.push_back("LN3");
 	firstNames.push_back("FN1");
 	firstNames.push_back("FN2");
+	firstNames.push_back("FN3");
 	addresses.push_back("ADDR1");
 	addresses.push_back("ADDR2");
+	addresses.push_back("ADDR3");
 	ages.push_back(1);
 	ages.push_back(2);
+	ages.push_back(3);
 	int count = 0, locCount = 0;
 	std::string result;
 
@@ -3258,7 +3277,7 @@ void SQLiteTest::testTransaction()
 		assertTrue (trans.isActive());
 
 		session << "SELECT COUNT(*) FROM Person", into(count), now;
-		assertTrue (2 == count);
+		assertTrue (3 == count);
 		assertTrue (session.isTransaction());
 		assertTrue (trans.isActive());
 		// no explicit commit, so transaction RAII must roll back here
@@ -3285,9 +3304,9 @@ void SQLiteTest::testTransaction()
 	}
 
 	session << "SELECT count(*) FROM Person", into(count), now;
-	assertTrue (2 == count);
+	assertTrue (3 == count);
 	local << "SELECT count(*) FROM Person", into(count), now;
-	assertTrue (2 == count);
+	assertTrue (3 == count);
 
 	session << "DELETE FROM Person", now;
 
@@ -3314,7 +3333,8 @@ void SQLiteTest::testTransaction()
 	session << "SELECT count(*) FROM Person", into(count), now;
 	assertTrue (0 == count);
 
-	trans.execute(sql);
+	bool status = trans.execute(sql);
+	assertTrue (status);
 
 	Statement stmt3 = (local << "SELECT COUNT(*) FROM Person", into(locCount), now);
 	assertTrue (2 == locCount);
@@ -3322,6 +3342,21 @@ void SQLiteTest::testTransaction()
 	session << "SELECT count(*) FROM Person", into(count), now;
 	assertTrue (2 == count);
 
+	session << "DELETE FROM Person", now;
+
+	std::string sql3 = format("INSERT INTO Pers VALUES ('%s','%s','%s',%d)", lastNames[2], firstNames[2], addresses[2], ages[2]);
+	// Table name is misspelled, should cause transaction rollback
+	sql.push_back(sql3);
+
+	std::string info;
+	status = trans.execute(sql, &info);
+
+	assertFalse (status);
+	assertEqual (info, "Invalid SQL statement: no such table: Pers: no such table: Pers");
+
+	session << "SELECT count(*) FROM Person", into(count), now;
+	assertTrue (0 == count);
+
 	session.close();
 	assertTrue (!session.isConnected());
 

+ 20 - 0
Data/include/Poco/Data/AbstractSessionImpl.h

@@ -115,6 +115,16 @@ public:
 	{
 	}
 
+	bool hasFeature(const std::string& name)
+		/// Looks a feature up in the features map
+		/// and returns true if there is one.
+	{
+		auto it = _features.find(name);
+		return it != _features.end() &&
+			it->second.getter &&
+			it->second.setter;
+	}
+
 	void setFeature(const std::string& name, bool state)
 		/// Looks a feature up in the features map
 		/// and calls the feature's setter, if there is one.
@@ -145,6 +155,16 @@ public:
 		else throw NotSupportedException(name);
 	}
 
+	bool hasProperty(const std::string& name)
+		/// Looks a property up in the properties map
+		/// and returns true if there is one.
+	{
+		auto it = _properties.find(name);
+		return it != _properties.end() &&
+			it->second.getter &&
+			it->second.setter;
+	}
+
 	void setProperty(const std::string& name, const Poco::Any& value)
 		/// Looks a property up in the properties map
 		/// and calls the property's setter, if there is one.

+ 2 - 1
Data/include/Poco/Data/ArchiveStrategy.h

@@ -180,7 +180,8 @@ public:
 	ArchiveByAgeStrategy(const std::string& connector,
 		const std::string& connect,
 		const std::string& sourceTable,
-		const std::string& destinationTable = DEFAULT_ARCHIVE_DESTINATION);
+		const std::string& destinationTable = DEFAULT_ARCHIVE_DESTINATION,
+		const std::string& age = "");
 
 	~ArchiveByAgeStrategy();
 

+ 2 - 0
Data/include/Poco/Data/PooledSessionImpl.h

@@ -62,8 +62,10 @@ public:
 	bool hasTransactionIsolation(Poco::UInt32) const;
 	bool isTransactionIsolation(Poco::UInt32) const;
 	const std::string& connectorName() const;
+	bool hasFeature(const std::string& name);
 	void setFeature(const std::string& name, bool state);
 	bool getFeature(const std::string& name);
+	bool hasProperty(const std::string& name);
 	void setProperty(const std::string& name, const Poco::Any& value);
 	Poco::Any getProperty(const std::string& name);
 

+ 2 - 1
Data/include/Poco/Data/Preparation.h

@@ -50,7 +50,8 @@ public:
 	void prepare()
 		/// Prepares data.
 	{
-		TypeHandler<T>::prepare(_pos, _val, preparation());
+		auto pPrep = preparation();
+		TypeHandler<T>::prepare(_pos, _val, pPrep);
 	}
 
 private:

+ 3 - 0
Data/include/Poco/Data/RecordSet.h

@@ -133,6 +133,9 @@ public:
 		/// for large recordsets, so it should be used judiciously.
 		/// Use totalRowCount() to obtain the total number of rows.
 
+	std::size_t affectedRowCount() const;
+		/// Returns the number of rows affected by the statement execution.
+
 	std::size_t extractedRowCount() const;
 		/// Returns the number of rows extracted during the last statement
 		/// execution.

+ 141 - 49
Data/include/Poco/Data/SQLChannel.h

@@ -22,18 +22,24 @@
 #include "Poco/Data/Connector.h"
 #include "Poco/Data/Session.h"
 #include "Poco/Data/Statement.h"
+#include "Poco/Logger.h"
 #include "Poco/Data/ArchiveStrategy.h"
 #include "Poco/Channel.h"
+#include "Poco/FileChannel.h"
 #include "Poco/Message.h"
 #include "Poco/AutoPtr.h"
 #include "Poco/String.h"
+#include "Poco/NotificationQueue.h"
+#include "Poco/Thread.h"
+#include "Poco/Mutex.h"
+#include <atomic>
 
 
 namespace Poco {
 namespace Data {
 
 
-class Data_API SQLChannel: public Poco::Channel
+class Data_API SQLChannel: public Poco::Channel, Poco::Runnable
 	/// This Channel implements logging to a SQL database.
 	/// The channel is dependent on the schema. The DDL for
 	/// table creation (subject to target DDL dialect dependent
@@ -61,23 +67,54 @@ class Data_API SQLChannel: public Poco::Channel
 	/// a risk of long blocking periods in case of remote server communication delays.
 {
 public:
-	using Ptr = Poco::AutoPtr<SQLChannel>;
+	class LogNotification : public Poco::Notification
+	{
+	public:
+		using Ptr = Poco::AutoPtr<LogNotification>;
+
+		LogNotification(const Poco::Message& message) :
+			_message(message)
+		{
+		}
+
+		const Poco::Message& message() const
+		{
+			return _message;
+		}
+
+	private:
+		Poco::Message _message;
+	};
+
+	static const int DEFAULT_MIN_BATCH_SIZE = 1;
+	static const int DEFAULT_MAX_BATCH_SIZE = 1000;
 
 	SQLChannel();
 		/// Creates SQLChannel.
 
 	SQLChannel(const std::string& connector,
 		const std::string& connect,
-		const std::string& name = "-");
-		/// Creates a SQLChannel with the given connector, connect string, timeout, table and name.
+		const std::string& name = "-",
+		const std::string& table = "T_POCO_LOG",
+		int timeout = 1000,
+		int minBatch = DEFAULT_MIN_BATCH_SIZE,
+		int maxBatch = DEFAULT_MAX_BATCH_SIZE);
+		/// Creates an SQLChannel with the given connector, connect string, timeout, table and name.
 		/// The connector must be already registered.
 
 	void open();
 		/// Opens the SQLChannel.
+		/// Returns true if succesful.
 
-	void close();
+	void close(int ms = 0);
 		/// Closes the SQLChannel.
 
+	void run();
+		/// Dequeues and sends the logs to the DB.
+
+	bool isRunning() const;
+		/// Returns true if the logging thread is running.
+
 	void log(const Message& msg);
 		/// Writes the log message to the database.
 
@@ -108,6 +145,8 @@ public:
 		///                  the target, the previous operation must have been either completed
 		///                  or timed out (see timeout and throw properties for details on
 		///                  how abnormal conditos are handled).
+		///                  This property is deprecated and has no effect - all logging
+		///                  is asynchronous since the 1.13.0. release.
 		///
 		///     * timeout:   Timeout (ms) to wait for previous log operation completion.
 		///                  Values "0" and "" mean no timeout. Only valid when logging
@@ -117,14 +156,35 @@ public:
 		///                  Setting this property to false may result in log entries being lost.
 		///                  True values are (case insensitive) "true", "t", "yes", "y".
 		///                  Anything else yields false.
+		///
+		///     * minBatch:  Minimal number of log entries to accumulate before actually sending
+		///                  logs to the destination.
+		///                  Defaults to 1 entry (for compatibility with older versions);
+		///                  can't be zero or larger than `maxBatch`.
+		///
+		///     * maxBatch:  Maximum number of log entries to accumulate. When the log queue
+		///                  reaches this size, log entries are silently discarded.
+		///                  Defaults to 100, can't be zero or larger than 1000.
+		///
+		///     * bulk:      Do bulk execute (on most DBMS systems, this can speed up things
+		///                  drastically).
+		///
+		///     * file       Destination file name for the backup FileChannel, used when DB
+		///                  connection is not present to log not executed SQL statements.
 
 	std::string getProperty(const std::string& name) const;
 		/// Returns the value of the property with the given name.
 
-	std::size_t wait();
+	void stop();
+		/// Stops and joins the logging thread..
+
+	std::size_t wait(int ms = 1000);
 		/// Waits for the completion of the previous operation and returns
 		/// the result. If chanel is in synchronous mode, returns 0 immediately.
 
+	size_t logged() const;
+		/// Returns the number of logged entries.
+
 	static void registerChannel();
 		/// Registers the channel with the global LoggingFactory.
 
@@ -136,56 +196,84 @@ public:
 	static const std::string PROP_MAX_AGE;
 	static const std::string PROP_ASYNC;
 	static const std::string PROP_TIMEOUT;
+	static const std::string PROP_MIN_BATCH;
+	static const std::string PROP_MAX_BATCH;
+	static const std::string PROP_BULK;
 	static const std::string PROP_THROW;
+	static const std::string PROP_FILE;
 
 protected:
 	~SQLChannel();
 
 private:
-	using SessionPtr = Poco::SharedPtr<Session>;
-	using StatementPtr = Poco::SharedPtr<Statement>;
-	using Priority = Poco::Message::Priority;
-	using StrategyPtr = Poco::SharedPtr<ArchiveStrategy>;
+	static const std::string SQL_INSERT_STMT;
+
+	typedef Poco::SharedPtr<Session>         SessionPtr;
+	typedef Poco::SharedPtr<Statement>       StatementPtr;
+	typedef Poco::Message::Priority          Priority;
+	typedef Poco::SharedPtr<ArchiveStrategy> StrategyPtr;
 
-	void initLogStatement();
-		/// Initiallizes the log statement.
+	void reconnect();
+		/// Closes and opens the DB connection.
 
-	void initArchiveStatements();
-	/// Initiallizes the archive statement.
+	bool processOne(int minBatch = 0);
+		/// Processes one message.
+		/// If the number of acummulated messages is greater
+		/// than minBatch, sends logs to the destination.
+		/// Returns true if log entry was processed.
 
-	void logAsync(const Message& msg);
-		/// Waits for previous operation completion and
-		/// calls logSync(). If the previous operation times out,
-		/// and _throw is true, TimeoutException is thrown, oterwise
-		/// the timeout is ignored and log entry is lost.
+	size_t execSQL();
+		/// Executes the log statement.
 
-	void logSync(const Message& msg);
-		/// Inserts the message in the target database.
+	size_t logSync();
+		/// Inserts entries into the target database.
 
 	bool isTrue(const std::string& value) const;
 		/// Returns true is value is "true", "t", "yes" or "y".
 		/// Case insensitive.
 
-	std::string  _connector;
-	std::string  _connect;
-	SessionPtr   _pSession;
-	StatementPtr _pLogStatement;
-	std::string  _name;
-	std::string  _table;
-	int          _timeout;
-	bool         _throw;
-	bool         _async;
-
-	// members for log entry cache (needed for async mode)
-	std::string _source;
-	long        _pid;
-	std::string _thread;
-	long        _tid;
-	int         _priority;
-	std::string _text;
-	DateTime    _dateTime;
-
-	StrategyPtr _pArchiveStrategy;
+	size_t logTofile(AutoPtr<FileChannel>& pFileChannel, const std::string& fileName, bool clear = false);
+		/// Logs cached entries to a file. Called in case DB insertions fail.
+
+	std::string maskPwd();
+		/// Masks the password in the connection
+		/// string, if detected. This is not a
+		/// bullet-proof method; if not succesful,
+		/// empty string is returned.
+
+	mutable Poco::FastMutex _mutex;
+
+	std::string      _connector;
+	std::string      _connect;
+	SessionPtr       _pSession;
+	std::string      _sql;
+	std::string      _name;
+	std::string      _table;
+	bool             _tableChanged;
+	int              _timeout;
+	std::atomic<int> _minBatch;
+	int              _maxBatch;
+	bool             _bulk;
+	std::atomic<bool> _throw;
+
+	// members for log entry cache
+	std::vector<std::string>      _source;
+	std::vector<long>             _pid;
+	std::vector<std::string>      _thread;
+	std::vector<long>             _tid;
+	std::vector<int>              _priority;
+	std::vector<std::string>      _text;
+	std::vector<DateTime>         _dateTime;
+	Poco::NotificationQueue       _logQueue;
+	std::unique_ptr<Poco::Thread> _pDBThread;
+	std::atomic<bool>             _reconnect;
+	std::atomic<bool>             _running;
+	std::atomic<bool>             _stop;
+	std::atomic<size_t>           _logged;
+	StrategyPtr                   _pArchiveStrategy;
+	std::string                   _file;
+	AutoPtr<FileChannel>          _pFileChannel;
+	Poco::Logger& _logger = Poco::Logger::get("SQLChannel");
 };
 
 
@@ -193,14 +281,6 @@ private:
 // inlines
 //
 
-inline std::size_t SQLChannel::wait()
-{
-	if (_async && _pLogStatement)
-		return _pLogStatement->wait(_timeout);
-
-	return 0;
-}
-
 
 inline bool SQLChannel::isTrue(const std::string& value) const
 {
@@ -211,6 +291,18 @@ inline bool SQLChannel::isTrue(const std::string& value) const
 }
 
 
+inline bool SQLChannel::isRunning() const
+{
+	return _running;
+}
+
+
+inline size_t SQLChannel::logged() const
+{
+	return _logged;
+}
+
+
 } } // namespace Poco::Data
 
 

+ 17 - 0
Data/include/Poco/Data/Session.h

@@ -273,6 +273,9 @@ public:
 		/// Utility function that teturns the URI formatted from supplied
 		/// arguments as "connector:///connectionString".
 
+	bool hasFeature(const std::string& name);
+		/// Returns true if session has the named feature.
+
 	void setFeature(const std::string& name, bool state);
 		/// Set the state of a feature.
 		///
@@ -291,6 +294,9 @@ public:
 		/// Throws a NotSupportedException if the requested feature is
 		/// not supported by the underlying implementation.
 
+	bool hasProperty(const std::string& name);
+		/// Returns true if session has the named property.
+
 	void setProperty(const std::string& name, const Poco::Any& value);
 		/// Set the value of a property.
 		///
@@ -456,6 +462,12 @@ inline std::string Session::uri() const
 }
 
 
+inline bool Session::hasFeature(const std::string& name)
+{
+	return _pImpl->hasFeature(name);
+}
+
+
 inline void Session::setFeature(const std::string& name, bool state)
 {
 	_pImpl->setFeature(name, state);
@@ -468,6 +480,11 @@ inline bool Session::getFeature(const std::string& name) const
 }
 
 
+inline bool Session::hasProperty(const std::string& name)
+{
+	return _pImpl->hasProperty(name);
+}
+
 inline void Session::setProperty(const std::string& name, const Poco::Any& value)
 {
 	_pImpl->setProperty(name, value);

+ 11 - 0
Data/include/Poco/Data/SessionImpl.h

@@ -53,6 +53,11 @@ public:
 	static const std::size_t CONNECTION_TIMEOUT_DEFAULT = CONNECTION_TIMEOUT_INFINITE;
 		/// Default connection/login timeout in seconds.
 
+	// ODBC only, otherwise no-op
+	static const int CURSOR_USE_ALWAYS = 0;
+	static const int CURSOR_USE_IF_NEEDED = 1;
+	static const int CURSOR_USE_NEVER = 2;
+
 	SessionImpl(const std::string& connectionString,
 		std::size_t timeout = LOGIN_TIMEOUT_DEFAULT);
 		/// Creates the SessionImpl.
@@ -141,6 +146,9 @@ public:
 	std::string uri() const;
 		/// Returns the URI for this session.
 
+	virtual bool hasFeature(const std::string& name) = 0;
+		/// Returns true if session has the named feature.
+
 	virtual void setFeature(const std::string& name, bool state) = 0;
 		/// Set the state of a feature.
 		///
@@ -159,6 +167,9 @@ public:
 		/// Throws a NotSupportedException if the requested feature is
 		/// not supported by the underlying implementation.
 
+	virtual bool hasProperty(const std::string& name) = 0;
+		/// Returns true if session has the named feature.
+
 	virtual void setProperty(const std::string& name, const Poco::Any& value) = 0;
 		/// Set the value of a property.
 		///

+ 15 - 15
Data/include/Poco/Data/SessionPool.h

@@ -191,21 +191,21 @@ private:
 
 	void closeAll(SessionList& sessionList);
 
-	std::string    _connector;
-	std::string    _connectionString;
-	int            _minSessions;
-	int            _maxSessions;
-	int            _idleTime;
-	int            _connTimeout;
-	int            _nSessions;
-	SessionList    _idleSessions;
-	SessionList    _activeSessions;
-	Poco::Timer    _janitorTimer;
-	FeatureMap     _featureMap;
-	PropertyMap    _propertyMap;
-	bool           _shutdown;
-	AddPropertyMap _addPropertyMap;
-	AddFeatureMap  _addFeatureMap;
+	std::string       _connector;
+	std::string       _connectionString;
+	std::atomic<int>  _minSessions;
+	std::atomic<int>  _maxSessions;
+	std::atomic<int>  _idleTime;
+	std::atomic<int>  _connTimeout;
+	std::atomic<int>  _nSessions;
+	SessionList       _idleSessions;
+	SessionList       _activeSessions;
+	Poco::Timer       _janitorTimer;
+	FeatureMap        _featureMap;
+	PropertyMap       _propertyMap;
+	std::atomic<bool> _shutdown;
+	AddPropertyMap    _addPropertyMap;
+	AddFeatureMap     _addFeatureMap;
 	mutable
 	Poco::Mutex _mutex;
 

+ 10 - 0
Data/include/Poco/Data/Statement.h

@@ -369,6 +369,10 @@ public:
 		/// Returns the number of rows extracted so far for the data set.
 		/// Default value indicates current data set (if any).
 
+	std::size_t affectedRowCount() const;
+		/// Returns the number of affected rows.
+		/// Used to find out the number of rows affected by insert, delete or update.
+
 	std::size_t extractionCount() const;
 		/// Returns the number of extraction storage buffers associated
 		/// with the current data set.
@@ -709,6 +713,12 @@ inline void Statement::setStorage(const std::string& storage)
 }
 
 
+inline std::size_t Statement::affectedRowCount() const
+{
+	return static_cast<std::size_t>(_pImpl->affectedRowCount());
+}
+
+
 inline std::size_t Statement::extractionCount() const
 {
 	return _pImpl->extractionCount();

+ 16 - 4
Data/include/Poco/Data/Transaction.h

@@ -39,6 +39,9 @@ class Data_API Transaction
 public:
 	Transaction(Poco::Data::Session& session, Poco::Logger* pLogger = 0);
 		/// Creates the Transaction and starts it, using the given database session and logger.
+		/// If `session` is in autocommit mode, it is switched to manual commit mode
+		/// for the duration of the transaction and reverted back to the original mode
+		/// after transaction completes.
 
 	Transaction(Poco::Data::Session& session, bool start);
 		/// Creates the Transaction, using the given database session.
@@ -107,10 +110,18 @@ public:
 		/// Passing true value for commit disables rollback during destruction
 		/// of this Transaction object.
 
-	void execute(const std::vector<std::string>& sql);
+	bool execute(const std::vector<std::string>& sql);
 		/// Executes all the SQL statements supplied in the vector and, after the last
-		/// one is sucesfully executed, commits the transaction.
-		/// If an error occurs during execution, transaction is rolled back.
+		/// one is sucesfully executed, commits the transaction and returns true.
+		/// If an error occurs during execution, transaction is rolled back and false is returned.
+		/// Passing true value for commit disables rollback during destruction
+		/// of this Transaction object.
+
+	bool execute(const std::vector<std::string>& sql, std::string* info);
+		/// Executes all the SQL statements supplied in the vector and, after the last
+		/// one is sucesfully executed, commits the transaction and returns true.
+		/// If an error occurs during execution, transaction is rolled back false is returned
+		/// and info pointer is assigned to a std::string containing the error message.
 		/// Passing true value for commit disables rollback during destruction
 		/// of this Transaction object.
 
@@ -148,7 +159,8 @@ private:
 		/// Otherwise does nothing.
 
 	Session _rSession;
-	Logger* _pLogger;
+	bool _autoCommit = false;
+	Logger* _pLogger = nullptr;
 };
 
 

+ 3 - 1
Data/src/ArchiveStrategy.cpp

@@ -64,10 +64,12 @@ void ArchiveStrategy::open()
 ArchiveByAgeStrategy::ArchiveByAgeStrategy(const std::string& connector,
 	const std::string& connect,
 	const std::string& sourceTable,
-	const std::string& destinationTable):
+	const std::string& destinationTable,
+	const std::string& age):
 	ArchiveStrategy(connector, connect, sourceTable, destinationTable)
 {
 	initStatements();
+	if (!age.empty()) setThreshold(age);
 }
 
 

+ 12 - 0
Data/src/PooledSessionImpl.cpp

@@ -166,6 +166,12 @@ const std::string& PooledSessionImpl::connectorName() const
 }
 
 
+bool PooledSessionImpl::hasFeature(const std::string& name)
+{
+	return access()->hasFeature(name);
+}
+
+
 void PooledSessionImpl::setFeature(const std::string& name, bool state)
 {
 	access()->setFeature(name, state);
@@ -178,6 +184,12 @@ bool PooledSessionImpl::getFeature(const std::string& name)
 }
 
 
+bool PooledSessionImpl::hasProperty(const std::string& name)
+{
+	return access()->hasProperty(name);
+}
+
+
 void PooledSessionImpl::setProperty(const std::string& name, const Poco::Any& value)
 {
 	access()->setProperty(name, value);

+ 8 - 1
Data/src/RecordSet.cpp

@@ -244,7 +244,8 @@ Row& RecordSet::row(std::size_t pos)
 
 std::size_t RecordSet::rowCount() const
 {
-	poco_assert (extractions().size());
+	if (extractions().size() == 0) return 0;
+
 	std::size_t rc = subTotalRowCount();
 	if (!isFiltered()) return rc;
 
@@ -258,6 +259,12 @@ std::size_t RecordSet::rowCount() const
 }
 
 
+std::size_t RecordSet::affectedRowCount() const
+{
+	return Statement::affectedRowCount();
+}
+
+
 bool RecordSet::isAllowed(std::size_t row) const
 {
 	if (!isFiltered()) return true;

+ 419 - 67
Data/src/SQLChannel.cpp

@@ -14,12 +14,18 @@
 
 #include "Poco/Data/SQLChannel.h"
 #include "Poco/Data/SessionFactory.h"
+#include "Poco/Data/BulkBinding.h"
 #include "Poco/DateTime.h"
+#include "Poco/DateTimeFormatter.h"
+#include "Poco/DateTimeFormat.h"
 #include "Poco/LoggingFactory.h"
 #include "Poco/Instantiator.h"
 #include "Poco/NumberParser.h"
 #include "Poco/NumberFormatter.h"
+#include "Poco/Stopwatch.h"
 #include "Poco/Format.h"
+#include "Poco/File.h"
+#include <fstream>
 
 
 namespace Poco {
@@ -37,37 +43,65 @@ const std::string SQLChannel::PROP_ARCHIVE_TABLE("archive");
 const std::string SQLChannel::PROP_MAX_AGE("keep");
 const std::string SQLChannel::PROP_ASYNC("async");
 const std::string SQLChannel::PROP_TIMEOUT("timeout");
+const std::string SQLChannel::PROP_MIN_BATCH("minBatch");
+const std::string SQLChannel::PROP_MAX_BATCH("maxBatch");
+const std::string SQLChannel::PROP_BULK("bulk");
 const std::string SQLChannel::PROP_THROW("throw");
+const std::string SQLChannel::PROP_FILE("file");
+
+
+const std::string SQLChannel::SQL_INSERT_STMT = "INSERT INTO %s " \
+	"(Source, Name, ProcessId, Thread, ThreadId, Priority, Text, DateTime)" \
+	" VALUES %s";
 
 
 SQLChannel::SQLChannel():
 	_name("-"),
 	_table("T_POCO_LOG"),
+	_tableChanged(true),
 	_timeout(1000),
-	_throw(true),
-	_async(true),
+	_minBatch(DEFAULT_MIN_BATCH_SIZE),
+	_maxBatch(DEFAULT_MAX_BATCH_SIZE),
+	_bulk(true),
+	_throw(false),
 	_pid(),
 	_tid(),
-	_priority()
+	_priority(),
+	_reconnect(false),
+	_running(false),
+	_stop(false),
+	_logged(0)
 {
 }
 
 
 SQLChannel::SQLChannel(const std::string& connector,
 	const std::string& connect,
-	const std::string& name):
+	const std::string& name,
+	const std::string& table,
+	int timeout,
+	int minBatch,
+	int maxBatch) :
 	_connector(connector),
 	_connect(connect),
 	_name(name),
-	_table("T_POCO_LOG"),
-	_timeout(1000),
-	_throw(true),
-	_async(true),
+	_table(table),
+	_tableChanged(true),
+	_timeout(timeout),
+	_minBatch(minBatch),
+	_maxBatch(maxBatch),
+	_bulk(false),
+	_throw(false),
 	_pid(),
 	_tid(),
-	_priority()
+	_priority(),
+	_pDBThread(new Thread),
+	_reconnect(true),
+	_running(false),
+	_stop(false),
+	_logged(0)
 {
-	open();
+	_pDBThread->start(*this);
 }
 
 
@@ -75,7 +109,11 @@ SQLChannel::~SQLChannel()
 {
 	try
 	{
-		close();
+		stop();
+		close(_timeout);
+		wait();
+		if (_pFileChannel)
+			_pFileChannel->close();
 	}
 	catch (...)
 	{
@@ -84,70 +122,177 @@ SQLChannel::~SQLChannel()
 }
 
 
-void SQLChannel::open()
+std::string SQLChannel::maskPwd()
 {
-	if (_connector.empty() || _connect.empty())
-		throw IllegalStateException("Connector and connect string must be non-empty.");
+	std::string displayConnect = _connect;
+	Poco::istring is1(displayConnect.c_str());
+	Poco::istring is2("pwd=");
+	std::size_t pos1 = Poco::isubstr(is1, is2);
+	if (pos1 == istring::npos)
+	{
+		is2 = "password=";
+		pos1 = Poco::isubstr(is1, is2);
+	}
+	if (pos1 != istring::npos)
+	{
+		pos1 += is2.length();
+		std::size_t pos2 = displayConnect.find(';', pos1);
+		if (pos2 != std::string::npos)
+		{
+			std::string toReplace = displayConnect.substr(pos1, pos2-pos1);
+			Poco::replaceInPlace(displayConnect, toReplace, std::string("***"));
+		}
+		else displayConnect.clear();
+	}
+	return displayConnect;
+}
 
-	_pSession = new Session(_connector, _connect);
-	initLogStatement();
+
+void SQLChannel::open()
+{
+	if (!_connector.empty() && !_connect.empty())
+	{
+		try
+		{
+			_pSession = new Session(_connector, _connect, _timeout / 1000);
+			if (_pSession->hasProperty("maxFieldSize")) _pSession->setProperty("maxFieldSize", 8192);
+			if (_pSession->hasProperty("autoBind")) _pSession->setFeature("autoBind", true);
+			_logger.information("Connected to %s: %s", _connector, maskPwd());
+			return;
+		}
+		catch (DataException& ex)
+		{
+			_logger.error(ex.displayText());
+		}
+	}
+	_pSession = nullptr;
+	return;
 }
 
 
-void SQLChannel::close()
+void SQLChannel::close(int ms)
 {
-	wait();
+	wait(ms);
+	_pSession = nullptr;
 }
 
 
 void SQLChannel::log(const Message& msg)
 {
-	if (_async) logAsync(msg);
-	else logSync(msg);
+	_logQueue.enqueueNotification(new LogNotification(msg));
 }
 
 
-void SQLChannel::logAsync(const Message& msg)
+size_t SQLChannel::logSync()
 {
-	poco_check_ptr (_pLogStatement);
-	if (0 == wait() && !_pLogStatement->done() && !_pLogStatement->initialized())
+	try
 	{
-		if (_throw)
-			throw TimeoutException("Timed out waiting for previous statement completion");
-		else return;
+		return execSQL();
+	}
+	catch (Exception&)
+	{
+		if (_throw) throw;
 	}
 
-	if (!_pSession || !_pSession->isConnected()) open();
-	logSync(msg);
+	return 0;
 }
 
 
-void SQLChannel::logSync(const Message& msg)
+bool SQLChannel::processOne(int minBatch)
 {
-	if (_pArchiveStrategy) _pArchiveStrategy->archive();
+	bool ret = false;
+	if (_logQueue.size())
+	{
+		Notification::Ptr pN = _logQueue.dequeueNotification();
+		LogNotification::Ptr pLN = pN.cast<LogNotification>();
+		if (pLN)
+		{
+			const Message& msg = pLN->message();
+			_source.push_back(msg.getSource());
+			if (_source.back().empty()) _source.back() = _name;
+			Poco::replaceInPlace(_source.back(), "'", "''");
+			_pid.push_back(msg.getPid());
+			_thread.push_back(msg.getThread());
+			Poco::replaceInPlace(_thread.back(), "'", "''");
+			_tid.push_back(msg.getTid());
+			_priority.push_back(msg.getPriority());
+			_text.push_back(msg.getText());
+			Poco::replaceInPlace(_text.back(), "'", "''");
+			_dateTime.push_back(msg.getTime());
+		}
+		ret = true;
+	}
+	if (_source.size() >= _minBatch) logSync();
 
-	_source = msg.getSource();
-	_pid = msg.getPid();
-	_thread = msg.getThread();
-	_tid = msg.getTid();
-	_priority = msg.getPriority();
-	_text = msg.getText();
-	_dateTime = msg.getTime();
-	if (_source.empty()) _source = _name;
+	return ret;
+}
 
-	try
+
+void SQLChannel::run()
+{
+	long sleepTime = 100; // milliseconds
+	while (!_stop)
+	{
+		try
+		{
+			if (_reconnect)
+			{
+				close(_timeout);
+				open();
+				_reconnect = _pSession.isNull();
+				if (_reconnect && sleepTime < 12800)
+					sleepTime *= 2;
+			}
+			processOne(_minBatch);
+			sleepTime = 100;
+		}
+		catch (Poco::Exception& ex)
+		{
+			_logger.error(ex.displayText());
+		}
+		catch (std::exception& ex)
+		{
+			_logger.error(ex.what());
+		}
+		catch (...)
+		{
+			_logger.error("SQLChannel::run(): unknown exception");
+		}
+		_running = true;
+		Thread::sleep(100);
+	}
+	_running = false;
+}
+
+
+void SQLChannel::stop()
+{
+	if (_pDBThread)
 	{
-		_pLogStatement->execute();
+		_reconnect = false;
+		_stop = true;
+		_pDBThread->join();
+		while (_logQueue.size())
+			processOne();
 	}
-	catch (Exception&)
+}
+
+
+void SQLChannel::reconnect()
+{
+	if (!_pDBThread)
 	{
-		if (_throw) throw;
+		_pDBThread.reset(new Thread);
+		_pDBThread->start(*this);
 	}
+	_reconnect = true;
 }
 
 
 void SQLChannel::setProperty(const std::string& name, const std::string& value)
 {
+	Poco::FastMutex::ScopedLock l(_mutex);
+
 	if (name == PROP_NAME)
 	{
 		_name = value;
@@ -156,17 +301,19 @@ void SQLChannel::setProperty(const std::string& name, const std::string& value)
 	else if (name == PROP_CONNECTOR)
 	{
 		_connector = value;
-		close(); open();
+		reconnect();
 	}
 	else if (name == PROP_CONNECT)
 	{
 		_connect = value;
-		close(); open();
+		reconnect();
 	}
 	else if (name == PROP_TABLE)
 	{
 		_table = value;
-		initLogStatement();
+		if (_pArchiveStrategy)
+			_pArchiveStrategy->setSource(value);
+		_tableChanged = true;
 	}
 	else if (name == PROP_ARCHIVE_TABLE)
 	{
@@ -180,7 +327,9 @@ void SQLChannel::setProperty(const std::string& name, const std::string& value)
 		}
 		else
 		{
-			_pArchiveStrategy = new ArchiveByAgeStrategy(_connector, _connect, _table, value);
+			std::string threshold;
+			if (_pArchiveStrategy) threshold = _pArchiveStrategy->getThreshold();
+			_pArchiveStrategy = new ArchiveByAgeStrategy(_connector, _connect, _table, value, threshold);
 		}
 	}
 	else if (name == PROP_MAX_AGE)
@@ -195,15 +344,14 @@ void SQLChannel::setProperty(const std::string& name, const std::string& value)
 		}
 		else
 		{
-			ArchiveByAgeStrategy* p = new ArchiveByAgeStrategy(_connector, _connect, _table);
-			p->setThreshold(value);
-			_pArchiveStrategy = p;
+			std::string destination = ArchiveByAgeStrategy::DEFAULT_ARCHIVE_DESTINATION;
+			if (_pArchiveStrategy) destination = _pArchiveStrategy->getDestination();
+			_pArchiveStrategy = new ArchiveByAgeStrategy(_connector, _connect, _table, destination, value);
 		}
 	}
 	else if (name == PROP_ASYNC)
 	{
-		_async = isTrue(value);
-		initLogStatement();
+		// no-op
 	}
 	else if (name == PROP_TIMEOUT)
 	{
@@ -212,10 +360,32 @@ void SQLChannel::setProperty(const std::string& name, const std::string& value)
 		else
 			_timeout = NumberParser::parse(value);
 	}
+	else if (name == PROP_MIN_BATCH)
+	{
+		int minBatch = NumberParser::parse(value);
+		if (!minBatch)
+			throw Poco::InvalidArgumentException(Poco::format("SQLChannel::setProperty(%s,%s)", name, value));
+		_minBatch = minBatch;
+	}
+	else if (name == PROP_MAX_BATCH)
+	{
+		int maxBatch = NumberParser::parse(value);
+		if (!maxBatch)
+			throw Poco::InvalidArgumentException(Poco::format("SQLChannel::setProperty(%s,%s)", name, value));
+		_maxBatch = maxBatch;
+	}
+	else if (name == PROP_BULK)
+	{
+		_bulk = isTrue(value);
+	}
 	else if (name == PROP_THROW)
 	{
 		_throw = isTrue(value);
 	}
+	else if (name == PROP_FILE)
+	{
+		_file = value;
+	}
 	else
 	{
 		Channel::setProperty(name, value);
@@ -225,6 +395,8 @@ void SQLChannel::setProperty(const std::string& name, const std::string& value)
 
 std::string SQLChannel::getProperty(const std::string& name) const
 {
+	Poco::FastMutex::ScopedLock l(_mutex);
+
 	if (name == PROP_NAME)
 	{
 		if (_name != "-") return _name;
@@ -254,11 +426,28 @@ std::string SQLChannel::getProperty(const std::string& name) const
 	{
 		return NumberFormatter::format(_timeout);
 	}
+	else if (name == PROP_MIN_BATCH)
+	{
+		return std::to_string(_minBatch);
+	}
+	else if (name == PROP_MAX_BATCH)
+	{
+		return std::to_string(_maxBatch);
+	}
+	else if (name == PROP_BULK)
+	{
+		if (_bulk) return "true";
+		else return "false";
+	}
 	else if (name == PROP_THROW)
 	{
 		if (_throw) return "true";
 		else return "false";
 	}
+	else if (name == PROP_FILE)
+	{
+		return _file;
+	}
 	else
 	{
 		return Channel::getProperty(name);
@@ -266,23 +455,186 @@ std::string SQLChannel::getProperty(const std::string& name) const
 }
 
 
-void SQLChannel::initLogStatement()
+size_t SQLChannel::logTofile(AutoPtr<FileChannel>& pFileChannel, const std::string& fileName, bool clear)
 {
-	_pLogStatement = new Statement(*_pSession);
-
-	std::string sql;
-	Poco::format(sql, "INSERT INTO %s VALUES (?,?,?,?,?,?,?,?)", _table);
-	*_pLogStatement << sql,
-		use(_source),
-		use(_name),
-		use(_pid),
-		use(_thread),
-		use(_tid),
-		use(_priority),
-		use(_text),
-		use(_dateTime);
-
-	if (_async) _pLogStatement->setAsync();
+	static std::vector<std::string> names;
+	if (names.size() != _source.size())
+		names.resize(_source.size(), Poco::replace(_name, "'", "''"));
+
+	std::size_t n = 0;
+
+	if (!pFileChannel) pFileChannel = new FileChannel(fileName);
+	if (pFileChannel)
+	{
+		std::string sql;
+		Poco::format(sql, SQL_INSERT_STMT, _table, std::string());
+		std::stringstream os;
+		os << sql << '\n';
+		auto it = _source.begin();
+		auto end = _source.end();
+		int idx = 0, batch = 0;
+		for (; it != end; ++idx)
+		{
+			std::string dt = Poco::DateTimeFormatter::format(_dateTime[idx], "%Y-%m-%d %H:%M:%S.%i");
+			os << "('" << *it << "','" <<
+				names[idx] << "'," <<
+				_pid[idx] << ",'" <<
+				_thread[idx] << "'," <<
+				_tid[idx] << ',' <<
+				_priority[idx] << ",'" <<
+				_text[idx] << "','" <<
+				dt << "')";
+			if (++batch == _maxBatch)
+			{
+				os << ";\n";
+				Message msg(_source[0], os.str(), Message::PRIO_ERROR);
+				pFileChannel->log(msg);
+				os.str(""); sql.clear();
+				Poco::format(sql, SQL_INSERT_STMT, _table, std::string());
+				batch = 0;
+			}
+			if (++it == end)
+			{
+				os << ";\n";
+				break;
+			}
+			os << ",\n";
+		}
+		Message msg(_source[0], os.str(), Message::PRIO_ERROR);
+		pFileChannel->log(msg);
+		n = _source.size();
+		if (clear && n)
+		{
+			_source.clear();
+			_pid.clear();
+			_thread.clear();
+			_tid.clear();
+			_priority.clear();
+			_text.clear();
+			_dateTime.clear();
+		}
+	}
+	return n;
+}
+
+
+size_t SQLChannel::execSQL()
+{
+	static std::vector<std::string> names;
+	if (names.size() != _source.size())
+		names.resize(_source.size(), Poco::replace(_name, "'", "''"));
+	static std::string placeholders = "(?,?,?,?,?,?,?,?)";
+
+	Poco::FastMutex::ScopedLock l(_mutex);
+
+	if (_tableChanged)
+	{
+		Poco::format(_sql, SQL_INSERT_STMT, _table, placeholders);
+		_tableChanged = false;
+	}
+
+	if (!_pSession || !_pSession->isConnected()) open();
+	if (_pArchiveStrategy) _pArchiveStrategy->archive();
+
+	size_t n = 0;
+	if (_pSession)
+	{
+		try
+		{
+			if (_bulk)
+			{
+				try
+				{
+					(*_pSession) << _sql,
+						use(_source, bulk),
+						use(names, bulk),
+						use(_pid, bulk),
+						use(_thread, bulk),
+						use(_tid, bulk),
+						use(_priority, bulk),
+						use(_text, bulk),
+						use(_dateTime, bulk), now;
+				}
+				// most likely bulk mode not supported,
+				// log and try again
+				catch (Poco::InvalidAccessException& ex)
+				{
+					_logger.log(ex);
+					(*_pSession) << _sql,
+						use(_source),
+						use(names),
+						use(_pid),
+						use(_thread),
+						use(_tid),
+						use(_priority),
+						use(_text),
+						use(_dateTime), now;
+					_bulk = false;
+				}
+			}
+			else
+			{
+				(*_pSession) << _sql,
+					use(_source),
+					use(names),
+					use(_pid),
+					use(_thread),
+					use(_tid),
+					use(_priority),
+					use(_text),
+					use(_dateTime), now;
+			}
+			n = _source.size();
+		}
+		catch (Poco::Exception& ex)
+		{
+			_logger.error(ex.displayText());
+			if (!_file.empty())
+				n = logTofile(_pFileChannel, _file);
+			close(_timeout);
+			_reconnect = true;
+		}
+		catch (std::exception& ex)
+		{
+			_logger.error(ex.what());
+			if (!_file.empty())
+				n = logTofile(_pFileChannel, _file);
+			close(_timeout);
+			_reconnect = true;
+		}
+	}
+	else
+	{
+		if (!_file.empty())
+			n = logTofile(_pFileChannel, _file);
+	}
+	if (n)
+	{
+		_logged += n;
+		_source.clear();
+		_pid.clear();
+		_thread.clear();
+		_tid.clear();
+		_priority.clear();
+		_text.clear();
+		_dateTime.clear();
+	}
+	return n;
+}
+
+
+std::size_t SQLChannel::wait(int ms)
+{
+	Stopwatch sw;
+	sw.start();
+	int processed = _logQueue.size();
+	while (_logQueue.size())
+	{
+		Thread::sleep(10);
+		if (ms && sw.elapsed() * 1000 > ms)
+			break;
+	}
+	return processed - _logQueue.size();
 }
 
 

+ 15 - 19
Data/src/SessionPool.cpp

@@ -64,9 +64,9 @@ Session SessionPool::get(const std::string& name, bool value)
 
 Session SessionPool::get()
 {
-	Poco::Mutex::ScopedLock lock(_mutex);
-    if (_shutdown) throw InvalidAccessException("Session pool has been shut down.");
+	if (_shutdown) throw InvalidAccessException("Session pool has been shut down.");
 
+	Poco::Mutex::ScopedLock lock(_mutex);
 	purgeDeadSessions();
 
 	if (_idleSessions.empty())
@@ -95,7 +95,6 @@ Session SessionPool::get()
 
 void SessionPool::purgeDeadSessions()
 {
-	Poco::Mutex::ScopedLock lock(_mutex);
 	if (_shutdown) return;
 
 	SessionList::iterator it = _idleSessions.begin();
@@ -139,9 +138,9 @@ int SessionPool::connTimeout() const
 
 int SessionPool::dead()
 {
-	Poco::Mutex::ScopedLock lock(_mutex);
 	int count = 0;
 
+	Poco::Mutex::ScopedLock lock(_mutex);
 	SessionList::iterator it = _activeSessions.begin();
 	SessionList::iterator itEnd = _activeSessions.end();
 	for (; it != itEnd; ++it)
@@ -156,7 +155,6 @@ int SessionPool::dead()
 
 int SessionPool::allocated() const
 {
-	Poco::Mutex::ScopedLock lock(_mutex);
 	return _nSessions;
 }
 
@@ -170,21 +168,24 @@ int SessionPool::available() const
 
 void SessionPool::setFeature(const std::string& name, bool state)
 {
-	Poco::Mutex::ScopedLock lock(_mutex);
 	if (_shutdown) throw InvalidAccessException("Session pool has been shut down.");
 
 	if (_nSessions > 0)
 		throw InvalidAccessException("Features can not be set after the first session was created.");
 
+	Poco::Mutex::ScopedLock lock(_mutex);
 	_featureMap.insert(FeatureMap::ValueType(name, state));
 }
 
 
 bool SessionPool::getFeature(const std::string& name)
 {
-	FeatureMap::ConstIterator it = _featureMap.find(name);
+
 	if (_shutdown) throw InvalidAccessException("Session pool has been shut down.");
 
+	Poco::Mutex::ScopedLock lock(_mutex);
+	FeatureMap::ConstIterator it = _featureMap.find(name);
+
 	if (_featureMap.end() == it)
 		throw NotFoundException("Feature not found:" + name);
 
@@ -194,18 +195,19 @@ bool SessionPool::getFeature(const std::string& name)
 
 void SessionPool::setProperty(const std::string& name, const Poco::Any& value)
 {
-	Poco::Mutex::ScopedLock lock(_mutex);
 	if (_shutdown) throw InvalidAccessException("Session pool has been shut down.");
 
 	if (_nSessions > 0)
 		throw InvalidAccessException("Properties can not be set after first session was created.");
 
+	Poco::Mutex::ScopedLock lock(_mutex);
 	_propertyMap.insert(PropertyMap::ValueType(name, value));
 }
 
 
 Poco::Any SessionPool::getProperty(const std::string& name)
 {
+	Poco::Mutex::ScopedLock lock(_mutex);
 	PropertyMap::ConstIterator it = _propertyMap.find(name);
 
 	if (_propertyMap.end() == it)
@@ -234,9 +236,9 @@ void SessionPool::customizeSession(Session&)
 
 void SessionPool::putBack(PooledSessionHolderPtr pHolder)
 {
-	Poco::Mutex::ScopedLock lock(_mutex);
 	if (_shutdown) return;
 
+	Poco::Mutex::ScopedLock lock(_mutex);
 	SessionList::iterator it = std::find(_activeSessions.begin(), _activeSessions.end(), pHolder);
 	if (it != _activeSessions.end())
 	{
@@ -287,9 +289,9 @@ void SessionPool::putBack(PooledSessionHolderPtr pHolder)
 
 void SessionPool::onJanitorTimer(Poco::Timer&)
 {
-	Poco::Mutex::ScopedLock lock(_mutex);
 	if (_shutdown) return;
 
+	Poco::Mutex::ScopedLock lock(_mutex);
 	SessionList::iterator it = _idleSessions.begin();
 	while (_nSessions > _minSessions && it != _idleSessions.end())
 	{
@@ -312,15 +314,9 @@ void SessionPool::onJanitorTimer(Poco::Timer&)
 
 void SessionPool::shutdown()
 {
-	{
-		Poco::Mutex::ScopedLock lock(_mutex);
-		if (_shutdown) return;
-		_shutdown = true;
-	}
-
+	if (_shutdown.exchange(true)) return;
+	_shutdown = true;
 	_janitorTimer.stop();
-
-	Poco::Mutex::ScopedLock lock(_mutex);
 	closeAll(_idleSessions);
 	closeAll(_activeSessions);
 }
@@ -344,4 +340,4 @@ void SessionPool::closeAll(SessionList& sessionList)
 }
 
 
-} } // namespace Poco::Data
+} } // namespace Poco::Data

+ 19 - 2
Data/src/Transaction.cpp

@@ -22,6 +22,7 @@ namespace Data {
 
 Transaction::Transaction(Poco::Data::Session& rSession, Poco::Logger* pLogger):
 	_rSession(rSession),
+	_autoCommit(_rSession.hasFeature("autoCommit") ? _rSession.getFeature("autoCommit") : false),
 	_pLogger(pLogger)
 {
 	begin();
@@ -30,6 +31,7 @@ Transaction::Transaction(Poco::Data::Session& rSession, Poco::Logger* pLogger):
 
 Transaction::Transaction(Poco::Data::Session& rSession, bool start):
 	_rSession(rSession),
+	_autoCommit(_rSession.hasFeature("autoCommit") ? _rSession.getFeature("autoCommit") : false),
 	_pLogger(0)
 {
 	if (start) begin();
@@ -71,7 +73,11 @@ Transaction::~Transaction()
 void Transaction::begin()
 {
 	if (!_rSession.isTransaction())
+	{
+		if (_autoCommit)
+			_rSession.setFeature("autoCommit", false);
 		_rSession.begin();
+	}
 	else
 		throw InvalidAccessException("Transaction in progress.");
 }
@@ -85,21 +91,28 @@ void Transaction::execute(const std::string& sql, bool doCommit)
 }
 
 
-void Transaction::execute(const std::vector<std::string>& sql)
+bool Transaction::execute(const std::vector<std::string>& sql)
+{
+	return execute(sql, nullptr);
+}
+
+bool Transaction::execute(const std::vector<std::string>& sql, std::string* info)
 {
 	try
 	{
 		std::vector<std::string>::const_iterator it = sql.begin();
 		std::vector<std::string>::const_iterator end = sql.end();
 		for (; it != end; ++it)	execute(*it, it + 1 == end ? true : false);
-		return;
+		return true;
 	}
 	catch (Exception& ex)
 	{
 		if (_pLogger) _pLogger->log(ex);
+		if(info) *info = ex.displayText();
 	}
 
 	rollback();
+	return false;
 }
 
 
@@ -109,6 +122,8 @@ void Transaction::commit()
 		_pLogger->debug("Committing transaction.");
 
 	_rSession.commit();
+	if (_autoCommit)
+		_rSession.setFeature("autoCommit", true);
 }
 
 
@@ -118,6 +133,8 @@ void Transaction::rollback()
 		_pLogger->debug("Rolling back transaction.");
 
 	_rSession.rollback();
+	if (_autoCommit)
+		_rSession.setFeature("autoCommit", true);
 }
 
 

+ 4 - 3
build/config/Linux

@@ -11,9 +11,10 @@
 LINKMODE ?= SHARED
 
 SANITIZEFLAGS ?=
-#-fsanitize=address
-#-fsanitize=undefined
-#-fsanitize=thread
+# sanitize flags:
+#  -fsanitize=address
+#  -fsanitize=undefined
+#  -fsanitize=thread
 
 #
 # Define Tools