From 5131fe1c15a10e5eee1558dffaced3adb21d77fa Mon Sep 17 00:00:00 2001 From: Aleksandar Fabijanic Date: Sun, 22 Oct 2023 12:53:54 +0200 Subject: [PATCH] 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 --- Data/ODBC/include/Poco/Data/ODBC/Binder.h | 75 +- .../include/Poco/Data/ODBC/ConnectionHandle.h | 55 +- .../ODBC/include/Poco/Data/ODBC/Diagnostics.h | 2 + .../Poco/Data/ODBC/EnvironmentHandle.h | 1 - Data/ODBC/include/Poco/Data/ODBC/Extractor.h | 4 +- .../Poco/Data/ODBC/ODBCStatementImpl.h | 34 +- Data/ODBC/include/Poco/Data/ODBC/Preparator.h | 8 +- .../ODBC/include/Poco/Data/ODBC/SessionImpl.h | 38 +- Data/ODBC/include/Poco/Data/ODBC/TypeInfo.h | 4 +- Data/ODBC/src/Binder.cpp | 98 +-- Data/ODBC/src/ConnectionHandle.cpp | 133 +++- Data/ODBC/src/EnvironmentHandle.cpp | 25 +- Data/ODBC/src/Extractor.cpp | 19 +- Data/ODBC/src/ODBCMetaColumn.cpp | 6 +- Data/ODBC/src/ODBCStatementImpl.cpp | 115 ++- Data/ODBC/src/Parameter.cpp | 2 +- Data/ODBC/src/Preparator.cpp | 7 +- Data/ODBC/src/SessionImpl.cpp | 178 +++-- Data/ODBC/src/TypeInfo.cpp | 11 +- Data/ODBC/testsuite/src/ODBCDB2Test.cpp | 3 + Data/ODBC/testsuite/src/ODBCMySQLTest.cpp | 3 + Data/ODBC/testsuite/src/ODBCOracleTest.cpp | 3 + .../ODBC/testsuite/src/ODBCPostgreSQLTest.cpp | 3 + Data/ODBC/testsuite/src/ODBCSQLServerTest.cpp | 648 ++++++++++------- Data/ODBC/testsuite/src/ODBCSQLServerTest.h | 16 +- Data/ODBC/testsuite/src/ODBCSQLiteTest.cpp | 3 + Data/ODBC/testsuite/src/ODBCTest.cpp | 35 +- Data/ODBC/testsuite/src/ODBCTest.h | 8 +- Data/ODBC/testsuite/src/SQLExecutor.cpp | 669 +++++++++++++++++- Data/ODBC/testsuite/src/SQLExecutor.h | 18 +- Data/SQLite/src/SessionImpl.cpp | 5 - Data/SQLite/src/Utility.cpp | 1 + Data/SQLite/testsuite/src/SQLiteTest.cpp | 125 ++-- Data/include/Poco/Data/AbstractSessionImpl.h | 20 + Data/include/Poco/Data/ArchiveStrategy.h | 3 +- Data/include/Poco/Data/PooledSessionImpl.h | 2 + Data/include/Poco/Data/Preparation.h | 3 +- Data/include/Poco/Data/RecordSet.h | 3 + Data/include/Poco/Data/SQLChannel.h | 186 +++-- Data/include/Poco/Data/Session.h | 17 + Data/include/Poco/Data/SessionImpl.h | 11 + Data/include/Poco/Data/SessionPool.h | 30 +- Data/include/Poco/Data/Statement.h | 10 + Data/include/Poco/Data/Transaction.h | 20 +- Data/src/ArchiveStrategy.cpp | 4 +- Data/src/PooledSessionImpl.cpp | 12 + Data/src/RecordSet.cpp | 9 +- Data/src/SQLChannel.cpp | 496 +++++++++++-- Data/src/SessionPool.cpp | 34 +- Data/src/Transaction.cpp | 21 +- build/config/Linux | 7 +- 51 files changed, 2462 insertions(+), 781 deletions(-) diff --git a/Data/ODBC/include/Poco/Data/ODBC/Binder.h b/Data/ODBC/include/Poco/Data/ODBC/Binder.h index 92fc6f1ed..0b34295fc 100644 --- a/Data/ODBC/include/Poco/Data/ODBC/Binder.h +++ b/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)"); } } @@ -672,7 +672,7 @@ private: { strSize = it->size() * sizeof(UTF16Char); if (strSize > size) - throw LengthExceededException("SQLBindParameter(std::vector)"); + throw LengthExceededException("ODBC::Binder::bindImplContainerUTF16String:SQLBindParameter(std::vector)"); 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)"); + throw StatementException(_rStmt, "ODBC::Binder::bindImplContainerUTF16String:SQLBindParameter(std::vector)"); } } @@ -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)"); + throw LengthExceededException("ODBC::Binder::bindImplContainerLOB():SQLBindParameter(std::vector)"); 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)"); + throw StatementException(_rStmt, "ODBC::Binder::bindImplContainerLOB():SQLBindParameter(std::vector)"); } } @@ -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. diff --git a/Data/ODBC/include/Poco/Data/ODBC/ConnectionHandle.h b/Data/ODBC/include/Poco/Data/ODBC/ConnectionHandle.h index a1a2390e3..a753fed8f 100644 --- a/Data/ODBC/include/Poco/Data/ODBC/ConnectionHandle.h +++ b/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(); diff --git a/Data/ODBC/include/Poco/Data/ODBC/Diagnostics.h b/Data/ODBC/include/Poco/Data/ODBC/Diagnostics.h index 818be4a1e..aa919c2be 100644 --- a/Data/ODBC/include/Poco/Data/ODBC/Diagnostics.h +++ b/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; diff --git a/Data/ODBC/include/Poco/Data/ODBC/EnvironmentHandle.h b/Data/ODBC/include/Poco/Data/ODBC/EnvironmentHandle.h index cec8d729e..292639ede 100644 --- a/Data/ODBC/include/Poco/Data/ODBC/EnvironmentHandle.h +++ b/Data/ODBC/include/Poco/Data/ODBC/EnvironmentHandle.h @@ -57,7 +57,6 @@ private: const EnvironmentHandle& operator=(const EnvironmentHandle&); SQLHENV _henv; - bool _isOwner; }; diff --git a/Data/ODBC/include/Poco/Data/ODBC/Extractor.h b/Data/ODBC/include/Poco/Data/ODBC/Extractor.h index eff01dda5..7488e8ff9 100644 --- a/Data/ODBC/include/Poco/Data/ODBC/Extractor.h +++ b/Data/ODBC/include/Poco/Data/ODBC/Extractor.h @@ -410,6 +410,7 @@ private: CharType** pc = AnyCast(&(_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(&(_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; diff --git a/Data/ODBC/include/Poco/Data/ODBC/ODBCStatementImpl.h b/Data/ODBC/include/Poco/Data/ODBC/ODBCStatementImpl.h index 0b7824c80..5eb4e3b19 100644 --- a/Data/ODBC/include/Poco/Data/ODBC/ODBCStatementImpl.h +++ b/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 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 _errorInfo; }; diff --git a/Data/ODBC/include/Poco/Data/ODBC/Preparator.h b/Data/ODBC/include/Poco/Data/ODBC/Preparator.h index ed1e77a6c..aa6ebc769 100644 --- a/Data/ODBC/include/Poco/Data/ODBC/Preparator.h +++ b/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()"); } } diff --git a/Data/ODBC/include/Poco/Data/ODBC/SessionImpl.h b/Data/ODBC/include/Poco/Data/ODBC/SessionImpl.h index d67269816..2e04a137a 100644 --- a/Data/ODBC/include/Poco/Data/ODBC/SessionImpl.h +++ b/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 _inTransaction; + int _queryTimeout; + std::string _dbEncoding; + Poco::FastMutex _mutex; }; diff --git a/Data/ODBC/include/Poco/Data/ODBC/TypeInfo.h b/Data/ODBC/include/Poco/Data/ODBC/TypeInfo.h index 043bf7e72..d6e25a6fa 100644 --- a/Data/ODBC/include/Poco/Data/ODBC/TypeInfo.h +++ b/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; }; diff --git a/Data/ODBC/src/Binder.cpp b/Data/ODBC/src/Binder.cpp index 9ac70c081..c3729af1c 100644 --- a/Data/ODBC/src/Binder.cpp +++ b/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(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(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(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(colSize) : static_cast(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(length); } diff --git a/Data/ODBC/src/ConnectionHandle.cpp b/Data/ODBC/src/ConnectionHandle.cpp index 3efc04824..f66550c72 100644 --- a/Data/ODBC/src/ConnectionHandle.cpp +++ b/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(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 diff --git a/Data/ODBC/src/EnvironmentHandle.cpp b/Data/ODBC/src/EnvironmentHandle.cpp index b9c19182a..71ebac6f8 100644 --- a/Data/ODBC/src/EnvironmentHandle.cpp +++ b/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 (...) diff --git a/Data/ODBC/src/Extractor.cpp b/Data/ODBC/src/Extractor.cpp index b5ce0ca4d..ccdac9339 100644 --- a/Data/ODBC/src/Extractor.cpp +++ b/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 @@ -300,10 +301,10 @@ bool Extractor::extractManualImpl(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(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(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(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(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(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; diff --git a/Data/ODBC/src/ODBCMetaColumn.cpp b/Data/ODBC/src/ODBCMetaColumn.cpp index debf3a1a3..e870762cf 100644 --- a/Data/ODBC/src/ODBCMetaColumn.cpp +++ b/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)); diff --git a/Data/ODBC/src/ODBCStatementImpl.cpp b/Data/ODBC/src/ODBCStatementImpl.cpp index e534308f9..ee4a0fcef 100644 --- a/Data/ODBC/src/ODBCStatementImpl.cpp +++ b/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(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(session().getProperty("maxFieldSize")); + std::size_t maxFieldSize; + try + { + maxFieldSize = AnyCast(session().getProperty("maxFieldSize")); + } + catch (Poco::BadCastException&) + { + maxFieldSize = AnyCast(session().getProperty("maxFieldSize")); + } _pBinder = new Binder(_stmt, maxFieldSize, bind, pDT, TextEncoding::find("UTF-8"), TextEncoding::find(Poco::RefAnyCast(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(session().getProperty("maxFieldSize")); + std::size_t maxFieldSize; + try + { + maxFieldSize = AnyCast(session().getProperty("maxFieldSize")); + } + catch (Poco::BadCastException&) + { + maxFieldSize = AnyCast(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(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(); } diff --git a/Data/ODBC/src/Parameter.cpp b/Data/ODBC/src/Parameter.cpp index b7dfe2970..00f8a0166 100644 --- a/Data/ODBC/src/Parameter.cpp +++ b/Data/ODBC/src/Parameter.cpp @@ -45,7 +45,7 @@ void Parameter::init() &_decimalDigits, &_isNullable))) { - throw StatementException(_rStmt); + throw StatementException(_rStmt, "ODBC::Parameter::init():SQLDescribeParam()"); } } diff --git a/Data/ODBC/src/Preparator.cpp b/Data/ODBC/src/Preparator.cpp index fa4f91f16..8431e16b6 100644 --- a/Data/ODBC/src/Preparator.cpp +++ b/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()"); } } diff --git a/Data/ODBC/src/SessionImpl.cpp b/Data/ODBC/src/SessionImpl.cpp index dc21541a1..644be1f3e 100644 --- a/Data/ODBC/src/SessionImpl.cpp +++ b/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(getLoginTimeout()); - if (Utility::isError(SQLSetConnectAttr(_db, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) tout, 0))) + + if (_db.connect(connectionString(), tout)) { - if (Utility::isError(SQLGetConnectAttr(_db, SQL_ATTR_LOGIN_TIMEOUT, &tout, 0, 0)) || - getLoginTimeout() != tout) - { - ConnectionError e(_db); - throw ConnectionFailedException(e.toString()); - } + setProperty("handle", _db.handle()); + + _dataTypes.fillTypeInfo(_db.pHandle()); + addProperty("dataTypeInfo", + &SessionImpl::setDataTypeInfo, + &SessionImpl::dataTypeInfo); + + addFeature("autoCommit", + &SessionImpl::autoCommit, + &SessionImpl::isAutoCommit); + + addFeature("autoBind", + &SessionImpl::autoBind, + &SessionImpl::isAutoBind); + + addFeature("autoExtract", + &SessionImpl::autoExtract, + &SessionImpl::isAutoExtract); + + addProperty("maxFieldSize", + &SessionImpl::setMaxFieldSize, + &SessionImpl::getMaxFieldSize); + + addProperty("queryTimeout", + &SessionImpl::setQueryTimeout, + &SessionImpl::getQueryTimeout); + + addProperty("dbEncoding", + &SessionImpl::setDBEncoding, + &SessionImpl::getDBEncoding); + + Poco::Data::ODBC::SQLSetConnectAttr(_db, SQL_ATTR_QUIET_MODE, 0, 0); + + if (!canTransact()) autoCommit("", true); } - - 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))) - { - ConnectionError err(_db); - std::string errStr = err.toString(); - close(); - throw ConnectionFailedException(errStr); - } - - _dataTypes.fillTypeInfo(_db); - addProperty("dataTypeInfo", - &SessionImpl::setDataTypeInfo, - &SessionImpl::dataTypeInfo); - - addFeature("autoCommit", - &SessionImpl::autoCommit, - &SessionImpl::isAutoCommit); - - addFeature("autoBind", - &SessionImpl::autoBind, - &SessionImpl::isAutoBind); - - addFeature("autoExtract", - &SessionImpl::autoExtract, - &SessionImpl::isAutoExtract); - - addProperty("maxFieldSize", - &SessionImpl::setMaxFieldSize, - &SessionImpl::getMaxFieldSize); - - addProperty("queryTimeout", - &SessionImpl::setQueryTimeout, - &SessionImpl::getQueryTimeout); - - addProperty("dbEncoding", - &SessionImpl::setDBEncoding, - &SessionImpl::getDBEncoding); - - Poco::Data::ODBC::SQLSetConnectAttr(_db, SQL_ATTR_QUIET_MODE, 0, 0); - - 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(Poco::AnyCast(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(_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); } diff --git a/Data/ODBC/src/TypeInfo.cpp b/Data/ODBC/src/TypeInfo.cpp index d667ad00b..8d4097a1d 100644 --- a/Data/ODBC/src/TypeInfo.cpp +++ b/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)) diff --git a/Data/ODBC/testsuite/src/ODBCDB2Test.cpp b/Data/ODBC/testsuite/src/ODBCDB2Test.cpp index c880ad6fb..f79d1ea40 100644 --- a/Data/ODBC/testsuite/src/ODBCDB2Test.cpp +++ b/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); diff --git a/Data/ODBC/testsuite/src/ODBCMySQLTest.cpp b/Data/ODBC/testsuite/src/ODBCMySQLTest.cpp index bd90d438a..340c98658 100644 --- a/Data/ODBC/testsuite/src/ODBCMySQLTest.cpp +++ b/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); diff --git a/Data/ODBC/testsuite/src/ODBCOracleTest.cpp b/Data/ODBC/testsuite/src/ODBCOracleTest.cpp index 29091efe3..bee9ea036 100644 --- a/Data/ODBC/testsuite/src/ODBCOracleTest.cpp +++ b/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); diff --git a/Data/ODBC/testsuite/src/ODBCPostgreSQLTest.cpp b/Data/ODBC/testsuite/src/ODBCPostgreSQLTest.cpp index 81d467b59..6aafef3d7 100644 --- a/Data/ODBC/testsuite/src/ODBCPostgreSQLTest.cpp +++ b/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); diff --git a/Data/ODBC/testsuite/src/ODBCSQLServerTest.cpp b/Data/ODBC/testsuite/src/ODBCSQLServerTest.cpp index 7dfe338f0..e87771997 100644 --- a/Data/ODBC/testsuite/src/ODBCSQLServerTest.cpp +++ b/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 testParams; session() << ("select * from #test;"), into(testParams), now; - assertEquals(1, testParams.size()); + assertEquals(1, static_cast(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)); + 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"); - - 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"); - - 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)); + for (int k = 0; k < 8;) + { + session().setFeature("autoBind", bindValue(k)); + session().setFeature("autoExtract", bindValue(k+1)); - recreatePersonTable(); - typedef Tuple Person; - std::vector 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; + recreatePersonTable(); + typedef Tuple Person; + std::vector 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"); - 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"); - 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)); + for (int k = 0; k < 8;) + { + session().setFeature("autoBind", bindValue(k)); + session().setFeature("autoExtract", bindValue(k+1)); - Any i = 2; - Any j = 0; + Any i = 2; + Any j = 0; - session() << "CREATE PROCEDURE storedProcedure(@inParam int, @outParam int OUTPUT) AS " - "BEGIN " - "SET @outParam = @inParam*@inParam; " - "END;" - , now; + 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(j)); - session() << "DROP PROCEDURE storedProcedure;", now; + session() << "{call storedProcedure(?, ?)}", in(i), out(j), now; + assertTrue (4 == AnyCast(j)); - session() << "CREATE PROCEDURE storedProcedure(@ioParam int OUTPUT) AS " - "BEGIN " - "SET @ioParam = @ioParam*@ioParam; " - "END;" - , now; + 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(i)); + i = 2; + session() << "{call storedProcedure(?)}", io(i), now; + assertTrue (4 == AnyCast(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)); + for (int k = 0; k < 8;) + { + session().setFeature("autoBind", bindValue(k)); - DynamicAny i = 2; - DynamicAny j = 0; + DynamicAny i = 2; + DynamicAny j = 0; - session() << "CREATE PROCEDURE storedProcedure(@inParam int, @outParam int OUTPUT) AS " - "BEGIN " - "SET @outParam = @inParam*@inParam; " - "END;" - , now; + 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); - session() << "DROP PROCEDURE storedProcedure;", now; + session() << "{call storedProcedure(?, ?)}", in(i), out(j), now; + assertTrue (4 == j); - session() << "CREATE PROCEDURE storedProcedure(@ioParam int OUTPUT) AS " - "BEGIN " - "SET @ioParam = @ioParam*@ioParam; " - "END;" - , now; + 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); + i = 2; + session() << "{call storedProcedure(?)}", io(i), now; + assertTrue (4 == i); + + k += 2; + } dropObject("PROCEDURE", "storedProcedure"); - - k += 2; } + catch (ConnectionException& ce) { std::cout << ce.toString() << std::endl; fail("testStoredProcedureDynamicAny()"); } + catch (StatementException& se) { std::cout << se.toString() << std::endl; fail("testStoredProcedureDynamicAny()"); } +} + + +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 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)); + for (int k = 0; k < 8;) + { + 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; + 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 storedFunction}", out(i), now; - assertTrue (-1 == i); - dropObject("PROCEDURE", "storedFunction"); + 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; - session() << "CREATE PROCEDURE storedFunction(@inParam 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); - 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 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; + 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); diff --git a/Data/ODBC/testsuite/src/ODBCSQLServerTest.h b/Data/ODBC/testsuite/src/ODBCSQLServerTest.h index c58b56c46..cb20f8449 100644 --- a/Data/ODBC/testsuite/src/ODBCSQLServerTest.h +++ b/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(); diff --git a/Data/ODBC/testsuite/src/ODBCSQLiteTest.cpp b/Data/ODBC/testsuite/src/ODBCSQLiteTest.cpp index 61b4bcf60..cbaf04bc1 100644 --- a/Data/ODBC/testsuite/src/ODBCSQLiteTest.cpp +++ b/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); diff --git a/Data/ODBC/testsuite/src/ODBCTest.cpp b/Data/ODBC/testsuite/src/ODBCTest.cpp index 6d5e98b7b..e1ce3c8c4 100644 --- a/Data/ODBC/testsuite/src/ODBCTest.cpp +++ b/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); diff --git a/Data/ODBC/testsuite/src/ODBCTest.h b/Data/ODBC/testsuite/src/ODBCTest.h index 4fe16f68c..9605b780b 100644 --- a/Data/ODBC/testsuite/src/ODBCTest.h +++ b/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(); diff --git a/Data/ODBC/testsuite/src/SQLExecutor.cpp b/Data/ODBC/testsuite/src/SQLExecutor.cpp index 84f44c7aa..e8dc060d1 100644 --- a/Data/ODBC/testsuite/src/SQLExecutor.cpp +++ b/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, ¶meterSize, &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, ¶meterSize, &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(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(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 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(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(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(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(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(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() == 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() == 1); + } + + { + Statement stmt = (session() << "SELECT Born FROM Person", now); + RecordSet rset(stmt); + assertTrue (rset.rowCount() == 1); + assertTrue (rset["Born"].convert() == 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() == 2); + } + + { + Statement stmt = (session() << "SELECT Born FROM Person ORDER BY Born DESC", now); + RecordSet rset(stmt); + assertTrue (rset.rowCount() == 2); + assertTrue (rset["Born"].convert() == born2); + rset.moveNext(); + assertTrue (rset["Born"].convert() == born); + rset.moveFirst(); + assertTrue (rset["Born"].convert() == born2); + rset.moveLast(); + assertTrue (rset["Born"].convert() == 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 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"); diff --git a/Data/ODBC/testsuite/src/SQLExecutor.h b/Data/ODBC/testsuite/src/SQLExecutor.h index 5f21805d7..1b6c399ec 100644 --- a/Data/ODBC/testsuite/src/SQLExecutor.h +++ b/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(); diff --git a/Data/SQLite/src/SessionImpl.cpp b/Data/SQLite/src/SessionImpl.cpp index da6c7dd89..d24c868cd 100644 --- a/Data/SQLite/src/SessionImpl.cpp +++ b/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."); } diff --git a/Data/SQLite/src/Utility.cpp b/Data/SQLite/src/Utility.cpp index d07426326..6593e1bf4 100644 --- a/Data/SQLite/src/Utility.cpp +++ b/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)); } } diff --git a/Data/SQLite/testsuite/src/SQLiteTest.cpp b/Data/SQLite/testsuite/src/SQLiteTest.cpp index eb257bf6e..c197db981 100755 --- a/Data/SQLite/testsuite/src/SQLiteTest.cpp +++ b/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 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 pSQLChannel = new SQLChannel(Poco::Data::SQLite::Connector::KEY, "dummy.db", "TestSQLChannel"); + Stopwatch sw; sw.start(); + while (!pSQLChannel->isRunning()) { - AutoPtr 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); - 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"]); + root.information("a Informational message"); + root.warning("b Warning message"); + root.debug("Debug message"); + + 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()); diff --git a/Data/include/Poco/Data/AbstractSessionImpl.h b/Data/include/Poco/Data/AbstractSessionImpl.h index 43a45fe72..78c65e119 100644 --- a/Data/include/Poco/Data/AbstractSessionImpl.h +++ b/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. diff --git a/Data/include/Poco/Data/ArchiveStrategy.h b/Data/include/Poco/Data/ArchiveStrategy.h index 24116efec..b0fe631a7 100644 --- a/Data/include/Poco/Data/ArchiveStrategy.h +++ b/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(); diff --git a/Data/include/Poco/Data/PooledSessionImpl.h b/Data/include/Poco/Data/PooledSessionImpl.h index 0d5edf578..0152a2df0 100644 --- a/Data/include/Poco/Data/PooledSessionImpl.h +++ b/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); diff --git a/Data/include/Poco/Data/Preparation.h b/Data/include/Poco/Data/Preparation.h index 870dfdcf2..20fd7c5f7 100644 --- a/Data/include/Poco/Data/Preparation.h +++ b/Data/include/Poco/Data/Preparation.h @@ -50,7 +50,8 @@ public: void prepare() /// Prepares data. { - TypeHandler::prepare(_pos, _val, preparation()); + auto pPrep = preparation(); + TypeHandler::prepare(_pos, _val, pPrep); } private: diff --git a/Data/include/Poco/Data/RecordSet.h b/Data/include/Poco/Data/RecordSet.h index 603ed52da..9d363d42d 100644 --- a/Data/include/Poco/Data/RecordSet.h +++ b/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. diff --git a/Data/include/Poco/Data/SQLChannel.h b/Data/include/Poco/Data/SQLChannel.h index 2a976871c..4c0f4183a 100644 --- a/Data/include/Poco/Data/SQLChannel.h +++ b/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 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; + class LogNotification : public Poco::Notification + { + public: + using Ptr = Poco::AutoPtr; + + 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; - using StatementPtr = Poco::SharedPtr; - using Priority = Poco::Message::Priority; - using StrategyPtr = Poco::SharedPtr; + static const std::string SQL_INSERT_STMT; - void initLogStatement(); - /// Initiallizes the log statement. + typedef Poco::SharedPtr SessionPtr; + typedef Poco::SharedPtr StatementPtr; + typedef Poco::Message::Priority Priority; + typedef Poco::SharedPtr StrategyPtr; - void initArchiveStatements(); - /// Initiallizes the archive statement. + void reconnect(); + /// Closes and opens the DB connection. - 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. + 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 logSync(const Message& msg); - /// Inserts the message in the target database. + size_t execSQL(); + /// Executes the log statement. + + 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; + size_t logTofile(AutoPtr& pFileChannel, const std::string& fileName, bool clear = false); + /// Logs cached entries to a file. Called in case DB insertions fail. - // 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; + 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. - StrategyPtr _pArchiveStrategy; + 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 _minBatch; + int _maxBatch; + bool _bulk; + std::atomic _throw; + + // members for log entry cache + std::vector _source; + std::vector _pid; + std::vector _thread; + std::vector _tid; + std::vector _priority; + std::vector _text; + std::vector _dateTime; + Poco::NotificationQueue _logQueue; + std::unique_ptr _pDBThread; + std::atomic _reconnect; + std::atomic _running; + std::atomic _stop; + std::atomic _logged; + StrategyPtr _pArchiveStrategy; + std::string _file; + AutoPtr _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 diff --git a/Data/include/Poco/Data/Session.h b/Data/include/Poco/Data/Session.h index 9fe700efa..1547fb53a 100644 --- a/Data/include/Poco/Data/Session.h +++ b/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); diff --git a/Data/include/Poco/Data/SessionImpl.h b/Data/include/Poco/Data/SessionImpl.h index 3b9625e9d..723084dbc 100644 --- a/Data/include/Poco/Data/SessionImpl.h +++ b/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. /// diff --git a/Data/include/Poco/Data/SessionPool.h b/Data/include/Poco/Data/SessionPool.h index 0c801c44b..9d01ba819 100644 --- a/Data/include/Poco/Data/SessionPool.h +++ b/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 _minSessions; + std::atomic _maxSessions; + std::atomic _idleTime; + std::atomic _connTimeout; + std::atomic _nSessions; + SessionList _idleSessions; + SessionList _activeSessions; + Poco::Timer _janitorTimer; + FeatureMap _featureMap; + PropertyMap _propertyMap; + std::atomic _shutdown; + AddPropertyMap _addPropertyMap; + AddFeatureMap _addFeatureMap; mutable Poco::Mutex _mutex; diff --git a/Data/include/Poco/Data/Statement.h b/Data/include/Poco/Data/Statement.h index 6644db31f..b400470cc 100644 --- a/Data/include/Poco/Data/Statement.h +++ b/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(_pImpl->affectedRowCount()); +} + + inline std::size_t Statement::extractionCount() const { return _pImpl->extractionCount(); diff --git a/Data/include/Poco/Data/Transaction.h b/Data/include/Poco/Data/Transaction.h index caf3e7f85..f08927383 100644 --- a/Data/include/Poco/Data/Transaction.h +++ b/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& sql); + bool execute(const std::vector& 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& 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; }; diff --git a/Data/src/ArchiveStrategy.cpp b/Data/src/ArchiveStrategy.cpp index c1cb8d995..e14856b0f 100644 --- a/Data/src/ArchiveStrategy.cpp +++ b/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); } diff --git a/Data/src/PooledSessionImpl.cpp b/Data/src/PooledSessionImpl.cpp index d5f5abf68..da41b8edf 100644 --- a/Data/src/PooledSessionImpl.cpp +++ b/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); diff --git a/Data/src/RecordSet.cpp b/Data/src/RecordSet.cpp index 20e978b0a..cb0987837 100644 --- a/Data/src/RecordSet.cpp +++ b/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; diff --git a/Data/src/SQLChannel.cpp b/Data/src/SQLChannel.cpp index 886b7c399..0b46562ab 100644 --- a/Data/src/SQLChannel.cpp +++ b/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 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."); - - _pSession = new Session(_connector, _connect); - initLogStatement(); + 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; } -void SQLChannel::close() +void SQLChannel::open() { - wait(); + 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(int ms) +{ + 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()) - { - if (_throw) - throw TimeoutException("Timed out waiting for previous statement completion"); - else return; - } - - if (!_pSession || !_pSession->isConnected()) open(); - logSync(msg); -} - - -void SQLChannel::logSync(const Message& msg) -{ - if (_pArchiveStrategy) _pArchiveStrategy->archive(); - - _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; - try { - _pLogStatement->execute(); + return execSQL(); } catch (Exception&) { if (_throw) throw; } + + return 0; +} + + +bool SQLChannel::processOne(int minBatch) +{ + bool ret = false; + if (_logQueue.size()) + { + Notification::Ptr pN = _logQueue.dequeueNotification(); + LogNotification::Ptr pLN = pN.cast(); + 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(); + + return ret; +} + + +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) + { + _reconnect = false; + _stop = true; + _pDBThread->join(); + while (_logQueue.size()) + processOne(); + } +} + + +void SQLChannel::reconnect() +{ + if (!_pDBThread) + { + _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& pFileChannel, const std::string& fileName, bool clear) { - _pLogStatement = new Statement(*_pSession); + static std::vector names; + if (names.size() != _source.size()) + names.resize(_source.size(), Poco::replace(_name, "'", "''")); - 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); + std::size_t n = 0; - if (_async) _pLogStatement->setAsync(); + 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 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(); } diff --git a/Data/src/SessionPool.cpp b/Data/src/SessionPool.cpp index 2c7e2bdd7..32b8312a5 100644 --- a/Data/src/SessionPool.cpp +++ b/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 \ No newline at end of file +} } // namespace Poco::Data diff --git a/Data/src/Transaction.cpp b/Data/src/Transaction.cpp index e66b6b3c8..28c066964 100644 --- a/Data/src/Transaction.cpp +++ b/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& sql) +bool Transaction::execute(const std::vector& sql) +{ + return execute(sql, nullptr); +} + +bool Transaction::execute(const std::vector& sql, std::string* info) { try { std::vector::const_iterator it = sql.begin(); std::vector::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); } diff --git a/build/config/Linux b/build/config/Linux index 84ecf7d47..83535dd42 100644 --- a/build/config/Linux +++ b/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