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
This commit is contained in:
Aleksandar Fabijanic 2023-10-22 12:53:54 +02:00 committed by GitHub
parent de04b9eac7
commit 5131fe1c15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 2462 additions and 781 deletions

View File

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

View File

@ -36,18 +36,40 @@ class ODBC_API ConnectionHandle
/// ODBC connection handle class /// ODBC connection handle class
{ {
public: public:
ConnectionHandle(EnvironmentHandle* pEnvironment = 0); ConnectionHandle(const std::string& connectString = "", SQLULEN timeout = 5);
/// Creates the ConnectionHandle. /// Creates the ConnectionHandle.
~ConnectionHandle(); ~ConnectionHandle();
/// Creates the ConnectionHandle. /// Creates the ConnectionHandle.
operator const SQLHDBC& () const; bool connect(const std::string& connectString = "", SQLULEN timeout = 5);
/// Const conversion operator into reference to native type. /// 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; const SQLHDBC& handle() const;
/// Returns const reference to handle; /// 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: private:
operator SQLHDBC& (); operator SQLHDBC& ();
/// Conversion operator into reference to native type. /// Conversion operator into reference to native type.
@ -55,17 +77,26 @@ private:
SQLHDBC& handle(); SQLHDBC& handle();
/// Returns reference to handle; /// Returns reference to handle;
void alloc();
/// Allocates the connection handle.
void free();
/// Frees the connection handle.
ConnectionHandle(const ConnectionHandle&); ConnectionHandle(const ConnectionHandle&);
const ConnectionHandle& operator=(const ConnectionHandle&); const ConnectionHandle& operator=(const ConnectionHandle&);
const EnvironmentHandle* _pEnvironment; const EnvironmentHandle* _pEnvironment = nullptr;
SQLHDBC _hdbc; SQLHDBC _hdbc = SQL_NULL_HDBC;
bool _ownsEnvironment; std::string _connectString;
friend class Poco::Data::ODBC::SessionImpl; friend class Poco::Data::ODBC::SessionImpl;
}; };
using Connection = ConnectionHandle;
// //
// inlines // 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 inline const SQLHDBC& ConnectionHandle::handle() const
{ {
return _hdbc; return _hdbc;
} }
inline const SQLHDBC* ConnectionHandle::pHandle() const
{
return &_hdbc;
}
inline ConnectionHandle::operator SQLHDBC& () inline ConnectionHandle::operator SQLHDBC& ()
{ {
return handle(); return handle();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -52,6 +52,13 @@ public:
ODBC_TXN_CAPABILITY_TRUE = 1 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, SessionImpl(const std::string& connect,
std::size_t loginTimeout, std::size_t loginTimeout,
std::size_t maxFieldSize = ODBC_MAX_FIELD_SIZE, std::size_t maxFieldSize = ODBC_MAX_FIELD_SIZE,
@ -159,6 +166,15 @@ public:
/// Returns the timeout (in seconds) for queries, /// Returns the timeout (in seconds) for queries,
/// or -1 if no timeout has been set. /// 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; int queryTimeout() const;
/// Returns the timeout (in seconds) for queries, /// Returns the timeout (in seconds) for queries,
/// or -1 if no timeout has been set. /// or -1 if no timeout has been set.
@ -195,17 +211,17 @@ private:
/// Sets the transaction isolation level. /// Sets the transaction isolation level.
/// Called internally from getTransactionIsolation() /// Called internally from getTransactionIsolation()
std::string _connector; std::string _connector;
mutable ConnectionHandle _db; mutable ConnectionHandle _db;
Poco::Any _maxFieldSize; Poco::Any _maxFieldSize;
bool _autoBind; bool _autoBind;
bool _autoExtract; bool _autoExtract;
TypeInfo _dataTypes; TypeInfo _dataTypes;
mutable char _canTransact; mutable TransactionCapability _canTransact;
bool _inTransaction; std::atomic<bool> _inTransaction;
int _queryTimeout; int _queryTimeout;
std::string _dbEncoding; std::string _dbEncoding;
Poco::FastMutex _mutex; Poco::FastMutex _mutex;
}; };

View File

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

View File

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

View File

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

View File

@ -26,15 +26,15 @@ namespace ODBC {
EnvironmentHandle::EnvironmentHandle(): _henv(SQL_NULL_HENV) EnvironmentHandle::EnvironmentHandle(): _henv(SQL_NULL_HENV)
{ {
if (Utility::isError(SQLAllocHandle(SQL_HANDLE_ENV, SQLRETURN rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &_henv);
SQL_NULL_HANDLE, if (Utility::isError(rc))
&_henv)) || throw ODBCException("EnvironmentHandle: Could not initialize ODBC environment.");
Utility::isError(SQLSetEnvAttr(_henv,
SQL_ATTR_ODBC_VERSION, rc = SQLSetEnvAttr(_henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, 0);
(SQLPOINTER) SQL_OV_ODBC3, if (Utility::isError(rc))
0)))
{ {
throw ODBCException("Could not initialize environment."); EnvironmentError err(_henv);
throw ODBCException(err.toString());
} }
} }
@ -43,10 +43,15 @@ EnvironmentHandle::~EnvironmentHandle()
{ {
try try
{ {
SQLRETURN rc = SQLFreeHandle(SQL_HANDLE_ENV, _henv);
#if defined(_DEBUG) #if defined(_DEBUG)
SQLRETURN rc = SQLFreeHandle(SQL_HANDLE_ENV, _henv);
if (Utility::isError(rc)) 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 #endif
} }
catch (...) catch (...)

View File

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

View File

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

View File

@ -47,13 +47,19 @@ ODBCStatementImpl::ODBCStatementImpl(SessionImpl& rSession):
_canCompile(true) _canCompile(true)
{ {
int queryTimeout = rSession.queryTimeout(); int queryTimeout = rSession.queryTimeout();
int rc = 0;
if (queryTimeout >= 0) if (queryTimeout >= 0)
{ {
SQLULEN uqt = static_cast<SQLULEN>(queryTimeout); SQLULEN uqt = static_cast<SQLULEN>(queryTimeout);
SQLSetStmtAttr(_stmt, rc = SQLSetStmtAttr(_stmt,
SQL_ATTR_QUERY_TIMEOUT, SQL_ATTR_QUERY_TIMEOUT,
(SQLPOINTER) uqt, (SQLPOINTER) uqt,
0); 0);
if (Utility::isError(rc))
{
throw ODBC::ConnectionException(_stmt,
Poco::format("SQLSetStmtAttr(SQL_ATTR_QUERY_TIMEOUT, %d)", queryTimeout));
}
} }
} }
@ -96,7 +102,15 @@ void ODBCStatementImpl::compileImpl()
{ {
} }
std::size_t maxFieldSize = AnyCast<std::size_t>(session().getProperty("maxFieldSize")); std::size_t maxFieldSize;
try
{
maxFieldSize = AnyCast<std::size_t>(session().getProperty("maxFieldSize"));
}
catch (Poco::BadCastException&)
{
maxFieldSize = AnyCast<int>(session().getProperty("maxFieldSize"));
}
_pBinder = new Binder(_stmt, maxFieldSize, bind, pDT, TextEncoding::find("UTF-8"), _pBinder = new Binder(_stmt, maxFieldSize, bind, pDT, TextEncoding::find("UTF-8"),
TextEncoding::find(Poco::RefAnyCast<std::string>(session().getProperty("dbEncoding")))); TextEncoding::find(Poco::RefAnyCast<std::string>(session().getProperty("dbEncoding"))));
@ -139,7 +153,15 @@ void ODBCStatementImpl::addPreparator()
Preparator::DataExtraction ext = session().getFeature("autoExtract") ? Preparator::DataExtraction ext = session().getFeature("autoExtract") ?
Preparator::DE_BOUND : Preparator::DE_MANUAL; Preparator::DE_BOUND : Preparator::DE_MANUAL;
std::size_t maxFieldSize = AnyCast<std::size_t>(session().getProperty("maxFieldSize")); std::size_t maxFieldSize;
try
{
maxFieldSize = AnyCast<std::size_t>(session().getProperty("maxFieldSize"));
}
catch (Poco::BadCastException&)
{
maxFieldSize = AnyCast<int>(session().getProperty("maxFieldSize"));
}
_preparations.push_back(new Preparator(_stmt, statement, maxFieldSize, ext)); _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() void ODBCStatementImpl::bindImpl()
{ {
doBind(); doBind();
@ -223,15 +277,16 @@ void ODBCStatementImpl::bindImpl()
SQLRETURN rc = SQLExecute(_stmt); SQLRETURN rc = SQLExecute(_stmt);
if (SQL_NEED_DATA == rc) putData(); if (SQL_NEED_DATA == rc) putData();
else checkError(rc, "SQLExecute()"); else checkError(rc, "ODBCStatementImpl::bindImpl():SQLExecute()");
_pBinder->synchronize(); _pBinder->synchronize();
} }
void ODBCStatementImpl::execDirectImpl(const std::string& query) void ODBCStatementImpl::execDirectImpl(const std::string& query)
{ {
SQLCHAR * statementText = (SQLCHAR*) query.c_str(); SQLCHAR * statementText = (SQLCHAR*) query.c_str();
SQLINTEGER textLength = query.size(); SQLINTEGER textLength = static_cast<SQLINTEGER>(query.size());
SQLRETURN rc = SQLExecDirect(_stmt,statementText,textLength); SQLRETURN rc = SQLExecDirect(_stmt,statementText,textLength);
checkError(rc, "SQLExecute()"); checkError(rc, "SQLExecute()");
@ -251,13 +306,13 @@ void ODBCStatementImpl::putData()
dataSize = (SQLINTEGER) _pBinder->parameterSize(pParam); dataSize = (SQLINTEGER) _pBinder->parameterSize(pParam);
if (Utility::isError(SQLPutData(_stmt, pParam, dataSize))) 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 else // if pParam is null pointer, do a dummy call
{ {
char dummy = 0; char dummy = 0;
if (Utility::isError(SQLPutData(_stmt, &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() void ODBCStatementImpl::clear()
{ {
SQLRETURN rc = SQLCloseCursor(_stmt);
_stepCalled = false; _stepCalled = false;
_affectedRowCount = 0; _affectedRowCount = 0;
_errorInfo.clear();
SQLRETURN rc = SQLFreeStmt(_stmt, SQL_CLOSE);
if (Utility::isError(rc)) if (Utility::isError(rc))
{ throw StatementException(_stmt, "ODBCStatementImpl::putData():SQLFreeStmt(SQL_CLOSE)");
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()");
}
} }
@ -335,6 +372,25 @@ void ODBCStatementImpl::makeStep()
{ {
_extractors[currentDataSet()]->reset(); _extractors[currentDataSet()]->reset();
_nextResponse = SQLFetch(_stmt); _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); checkError(_nextResponse);
_stepCalled = true; _stepCalled = true;
} }
@ -363,7 +419,7 @@ std::size_t ODBCStatementImpl::next()
else else
{ {
throw StatementException(_stmt, throw StatementException(_stmt,
std::string("Next row not available.")); "ODBCStatementImpl::next():Next row not available.");
} }
return count; return count;
@ -416,6 +472,7 @@ void ODBCStatementImpl::checkError(SQLRETURN rc, const std::string& msg)
throw StatementException(_stmt, str); throw StatementException(_stmt, str);
} }
else if (SQL_SUCCESS_WITH_INFO == rc) addErrors();
} }

View File

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

View File

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

View File

@ -43,8 +43,10 @@ SessionImpl::SessionImpl(const std::string& connect,
_dbEncoding("UTF-8") _dbEncoding("UTF-8")
{ {
setFeature("bulk", true); 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(); open();
setProperty("handle", _db.handle());
} }
@ -63,8 +65,10 @@ SessionImpl::SessionImpl(const std::string& connect,
_dbEncoding("UTF-8") _dbEncoding("UTF-8")
{ {
setFeature("bulk", true); 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(); open();
setProperty("handle", _db.handle());
} }
@ -104,69 +108,51 @@ void SessionImpl::open(const std::string& connect)
setConnectionString(connect); setConnectionString(connect);
} }
poco_assert_dbg (!connectionString().empty()); if (connectionString().empty())
throw InvalidArgumentException("SessionImpl::open(): Connection string empty");
SQLULEN tout = static_cast<SQLULEN>(getLoginTimeout()); SQLULEN tout = static_cast<SQLULEN>(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)) || setProperty("handle", _db.handle());
getLoginTimeout() != tout)
{ _dataTypes.fillTypeInfo(_db.pHandle());
ConnectionError e(_db); addProperty("dataTypeInfo",
throw ConnectionFailedException(e.toString()); &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
SQLCHAR connectOutput[512] = {0}; throw ConnectionException(SQL_NULL_HDBC,
SQLSMALLINT result; Poco::format("Connection to '%s' failed.", connectionString()));
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);
} }
@ -180,15 +166,62 @@ void SessionImpl::setDBEncoding(const std::string&, const Poco::Any& value)
bool SessionImpl::isConnected() const bool SessionImpl::isConnected() const
{ {
SQLULEN value = 0; return _db.isConnected();
}
if (Utility::isError(Poco::Data::ODBC::SQLGetConnectAttr(_db,
SQL_ATTR_CONNECTION_DEAD,
&value,
0,
0))) return false;
return (SQL_CD_FALSE == value); inline void SessionImpl::setCursorUse(const std::string&, const Poco::Any& value)
{
#if POCO_OS == POCO_OS_WINDOWS_NT
#pragma warning (disable : 4995) // ignore marked as deprecated
#endif
int cursorUse = static_cast<int>(Poco::AnyCast<CursorUse>(value));
int rc = 0;
switch (cursorUse)
{
case ODBC_CURSOR_USE_ALWAYS:
rc = Poco::Data::ODBC::SQLSetConnectAttr(_db, SQL_ATTR_ODBC_CURSORS, (SQLPOINTER)SQL_CUR_USE_ODBC, SQL_IS_INTEGER);
break;
case ODBC_CURSOR_USE_IF_NEEDED:
rc = Poco::Data::ODBC::SQLSetConnectAttr(_db, SQL_ATTR_ODBC_CURSORS, (SQLPOINTER)SQL_CUR_USE_IF_NEEDED, SQL_IS_INTEGER);
break;
case ODBC_CURSOR_USE_NEVER:
rc = Poco::Data::ODBC::SQLSetConnectAttr(_db, SQL_ATTR_ODBC_CURSORS, (SQLPOINTER)SQL_CUR_USE_DRIVER, SQL_IS_INTEGER);
break;
default:
throw Poco::InvalidArgumentException(Poco::format("SessionImpl::setCursorUse(%d)", cursorUse));
}
#if POCO_OS == POCO_OS_WINDOWS_NT
#pragma warning (default : 4995)
#endif
if (Utility::isError(rc))
{
throw Poco::Data::ODBC::HandleException<SQLHDBC, SQL_HANDLE_DBC>(_db, Poco::format("SessionImpl::setCursorUse(%d)", cursorUse));
}
}
inline Poco::Any SessionImpl::getCursorUse(const std::string&) const
{
#if POCO_OS == POCO_OS_WINDOWS_NT
#pragma warning (disable : 4995) // ignore marked as deprecated
#endif
SQLUINTEGER curUse = 0;
Poco::Data::ODBC::SQLGetConnectAttr(_db, SQL_ATTR_ODBC_CURSORS, &curUse, SQL_IS_UINTEGER, 0);
switch (curUse)
{
case SQL_CUR_USE_ODBC:
return ODBC_CURSOR_USE_ALWAYS;
case SQL_CUR_USE_IF_NEEDED:
return ODBC_CURSOR_USE_IF_NEEDED;
case SQL_CUR_USE_DRIVER:
return ODBC_CURSOR_USE_NEVER;
default: break;
}
throw Poco::InvalidArgumentException(Poco::format("SessionImpl::getCursorUse(%u)", curUse));
#if POCO_OS == POCO_OS_WINDOWS_NT
#pragma warning (default : 4995)
#endif
} }
@ -333,12 +366,14 @@ void SessionImpl::autoCommit(const std::string&, bool val)
SQL_ATTR_AUTOCOMMIT, SQL_ATTR_AUTOCOMMIT,
val ? (SQLPOINTER) SQL_AUTOCOMMIT_ON : val ? (SQLPOINTER) SQL_AUTOCOMMIT_ON :
(SQLPOINTER) SQL_AUTOCOMMIT_OFF, (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 bool SessionImpl::isAutoCommit(const std::string&) const
{ {
if (!_db) return false;
SQLULEN value = 0; SQLULEN value = 0;
checkError(Poco::Data::ODBC::SQLGetConnectAttr(_db, checkError(Poco::Data::ODBC::SQLGetConnectAttr(_db,
@ -353,7 +388,7 @@ bool SessionImpl::isAutoCommit(const std::string&) const
bool SessionImpl::isTransaction() const bool SessionImpl::isTransaction() const
{ {
if (!canTransact()) return false; if (!_db || !canTransact()) return false;
SQLULEN value = 0; SQLULEN value = 0;
checkError(Poco::Data::ODBC::SQLGetConnectAttr(_db, checkError(Poco::Data::ODBC::SQLGetConnectAttr(_db,
@ -419,7 +454,8 @@ void SessionImpl::close()
{ {
} }
SQLDisconnect(_db); _db.disconnect();
setProperty("handle", SQL_NULL_HDBC);
} }

View File

@ -27,7 +27,7 @@ TypeInfo::TypeInfo(SQLHDBC* pHDBC): _pHDBC(pHDBC)
{ {
fillCTypes(); fillCTypes();
fillSQLTypes(); fillSQLTypes();
if (_pHDBC) fillTypeInfo(*_pHDBC); if (_pHDBC) fillTypeInfo(_pHDBC);
} }
@ -62,7 +62,8 @@ void TypeInfo::fillCTypes()
void TypeInfo::fillSQLTypes() 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_BIT, SQL_BIT));
_sqlDataTypes.insert(ValueType(SQL_C_TINYINT, SQL_TINYINT)); _sqlDataTypes.insert(ValueType(SQL_C_TINYINT, SQL_TINYINT));
_sqlDataTypes.insert(ValueType(SQL_C_STINYINT, 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) if (_typeInfo.empty() && _pHDBC)
{ {
@ -98,7 +99,7 @@ void TypeInfo::fillTypeInfo(SQLHDBC pHDBC)
rc = SQLAllocHandle(SQL_HANDLE_STMT, *_pHDBC, &hstmt); rc = SQLAllocHandle(SQL_HANDLE_STMT, *_pHDBC, &hstmt);
if (!SQL_SUCCEEDED(rc)) if (!SQL_SUCCEEDED(rc))
throw StatementException(hstmt, "SQLGetData()"); throw StatementException(hstmt, "ODBC::Preparator::fillTypeInfo():SQLGetData()");
rc = SQLGetTypeInfo(hstmt, SQL_ALL_TYPES); rc = SQLGetTypeInfo(hstmt, SQL_ALL_TYPES);
if (SQL_SUCCEEDED(rc)) if (SQL_SUCCEEDED(rc))

View File

@ -594,6 +594,9 @@ CppUnit::Test* ODBCDB2Test::suite()
CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCDB2Test"); CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCDB2Test");
CppUnit_addTest(pSuite, ODBCDB2Test, testBareboneODBC); 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, testZeroRows);
CppUnit_addTest(pSuite, ODBCDB2Test, testSimpleAccess); CppUnit_addTest(pSuite, ODBCDB2Test, testSimpleAccess);
CppUnit_addTest(pSuite, ODBCDB2Test, testComplexType); CppUnit_addTest(pSuite, ODBCDB2Test, testComplexType);

View File

@ -421,6 +421,9 @@ CppUnit::Test* ODBCMySQLTest::suite()
CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCMySQLTest"); CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCMySQLTest");
CppUnit_addTest(pSuite, ODBCMySQLTest, testBareboneODBC); 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, testZeroRows);
CppUnit_addTest(pSuite, ODBCMySQLTest, testSimpleAccess); CppUnit_addTest(pSuite, ODBCMySQLTest, testSimpleAccess);
CppUnit_addTest(pSuite, ODBCMySQLTest, testComplexType); CppUnit_addTest(pSuite, ODBCMySQLTest, testComplexType);

View File

@ -854,6 +854,9 @@ CppUnit::Test* ODBCOracleTest::suite()
CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCOracleTest"); CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCOracleTest");
CppUnit_addTest(pSuite, ODBCOracleTest, testBareboneODBC); 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, testZeroRows);
CppUnit_addTest(pSuite, ODBCOracleTest, testSimpleAccess); CppUnit_addTest(pSuite, ODBCOracleTest, testSimpleAccess);
CppUnit_addTest(pSuite, ODBCOracleTest, testComplexType); CppUnit_addTest(pSuite, ODBCOracleTest, testComplexType);

View File

@ -583,6 +583,9 @@ CppUnit::Test* ODBCPostgreSQLTest::suite()
CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCPostgreSQLTest"); CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCPostgreSQLTest");
CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testBareboneODBC); 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, testZeroRows);
CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testSimpleAccess); CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testSimpleAccess);
CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testComplexType); CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testComplexType);

View File

@ -47,39 +47,26 @@ using Poco::DateTime;
// uncomment to use native SQL driver // uncomment to use native SQL driver
//#define POCO_ODBC_USE_SQL_NATIVE //#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) // (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 #ifdef POCO_ODBC_USE_SQL_NATIVE
#define MS_SQL_SERVER_ODBC_DRIVER "SQL Server Native Client 10.0" #define MS_SQL_SERVER_ODBC_DRIVER "SQL Server Native Client 10.0"
#else #else
#define MS_SQL_SERVER_ODBC_DRIVER "SQL Server" #define MS_SQL_SERVER_ODBC_DRIVER "ODBC Driver 18 for SQL Server"
#endif #endif
#pragma message ("Using " MS_SQL_SERVER_ODBC_DRIVER " driver") #pragma message ("Using " MS_SQL_SERVER_ODBC_DRIVER " driver")
#else #else
#define MS_SQL_SERVER_ODBC_DRIVER "FreeTDS" #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) #if defined(POCO_OS_FAMILY_WINDOWS)
#pragma message ("Using " MS_SQL_SERVER_ODBC_DRIVER " driver, version " FREE_TDS_VERSION) #pragma message ("Using " MS_SQL_SERVER_ODBC_DRIVER " driver, version " FREE_TDS_VERSION)
#endif #endif
#endif #endif
#define MS_SQL_SERVER_DSN "PocoDataSQLServerTest" #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_PORT "1433"
#define MS_SQL_SERVER_DB "poco" #define MS_SQL_SERVER_DB "poco"
#define MS_SQL_SERVER_UID "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 ";" "DATABASE=" MS_SQL_SERVER_DB ";"
"SERVER=" MS_SQL_SERVER_SERVER ";" "SERVER=" MS_SQL_SERVER_SERVER ";"
"PORT=" MS_SQL_SERVER_PORT ";" "PORT=" MS_SQL_SERVER_PORT ";"
"Encrypt=no"
#ifdef FREE_TDS_VERSION #ifdef FREE_TDS_VERSION
"TDS_Version=" FREE_TDS_VERSION ";" "TDS_Version=" FREE_TDS_VERSION ";"
#endif #endif
@ -120,7 +108,7 @@ ODBCSQLServerTest::~ODBCSQLServerTest()
void ODBCSQLServerTest::testBareboneODBC() void ODBCSQLServerTest::testBareboneODBC()
{ {
std::string tableCreateString = "CREATE TABLE Test " std::string createString = "CREATE TABLE Test "
"(First VARCHAR(30)," "(First VARCHAR(30),"
"Second VARCHAR(30)," "Second VARCHAR(30),"
"Third VARBINARY(30)," "Third VARBINARY(30),"
@ -128,24 +116,47 @@ void ODBCSQLServerTest::testBareboneODBC()
"Fifth FLOAT," "Fifth FLOAT,"
"Sixth DATETIME)"; "Sixth DATETIME)";
executor().bareboneODBCTest(dbConnString(), tableCreateString, executor().bareboneODBCTest(dbConnString(), createString,
SQLExecutor::PB_IMMEDIATE, SQLExecutor::DE_MANUAL, true, "CONVERT(VARBINARY(30),?)"); 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),?)"); 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),?)"); 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),?)"); SQLExecutor::PB_AT_EXEC, SQLExecutor::DE_BOUND, true, "CONVERT(VARBINARY(30),?)");
tableCreateString = "CREATE TABLE Test " createString = "CREATE TABLE Test "
"(First VARCHAR(30)," "(First VARCHAR(30),"
"Second INTEGER," "Second INTEGER,"
"Third FLOAT)"; "Third FLOAT)";
executor().bareboneODBCMultiResultTest(dbConnString(), tableCreateString, SQLExecutor::PB_IMMEDIATE, SQLExecutor::DE_MANUAL); executor().bareboneODBCMultiResultTest(dbConnString(), createString, SQLExecutor::PB_IMMEDIATE, SQLExecutor::DE_MANUAL);
executor().bareboneODBCMultiResultTest(dbConnString(), tableCreateString, SQLExecutor::PB_IMMEDIATE, SQLExecutor::DE_BOUND); executor().bareboneODBCMultiResultTest(dbConnString(), createString, SQLExecutor::PB_IMMEDIATE, SQLExecutor::DE_BOUND);
executor().bareboneODBCMultiResultTest(dbConnString(), tableCreateString, SQLExecutor::PB_AT_EXEC, SQLExecutor::DE_MANUAL); executor().bareboneODBCMultiResultTest(dbConnString(), createString, SQLExecutor::PB_AT_EXEC, SQLExecutor::DE_MANUAL);
executor().bareboneODBCMultiResultTest(dbConnString(), tableCreateString, SQLExecutor::PB_AT_EXEC, SQLExecutor::DE_BOUND); 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() void ODBCSQLServerTest::testTempTable()
@ -161,13 +172,14 @@ void ODBCSQLServerTest::testTempTable()
std::vector<testParam> testParams; std::vector<testParam> testParams;
session() << ("select * from #test;"), into(testParams), now; session() << ("select * from #test;"), into(testParams), now;
assertEquals(1, testParams.size()); assertEquals(1, static_cast<long>(testParams.size()));
assertEquals(1, testParams.front().get<0>()); assertEquals(1, testParams.front().get<0>());
assertEquals(2, testParams.front().get<1>()); assertEquals(2, testParams.front().get<1>());
assertEquals(3, testParams.front().get<2>()); assertEquals(3, testParams.front().get<2>());
} }
void ODBCSQLServerTest::testBLOB() void ODBCSQLServerTest::testBLOB()
{ {
const std::size_t maxFldSize = 250000; const std::size_t maxFldSize = 250000;
@ -262,285 +274,386 @@ void ODBCSQLServerTest::testBulk()
void ODBCSQLServerTest::testStoredProcedure() void ODBCSQLServerTest::testStoredProcedure()
{ {
for (int k = 0; k < 8;) try
{ {
session().setFeature("autoBind", bindValue(k)); for (int k = 0; k < 8;)
session().setFeature("autoExtract", bindValue(k+1)); {
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"); 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: catch (ConnectionException& ce) { std::cout << ce.toString() << std::endl; fail("testStoredProcedure()"); }
catch (StatementException& se) { std::cout << se.toString() << std::endl; fail("testStoredProcedure()"); }
[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");
*/
} }
void ODBCSQLServerTest::testCursorStoredProcedure() void ODBCSQLServerTest::testCursorStoredProcedure()
{ {
for (int k = 0; k < 8;) try
{ {
session().setFeature("autoBind", bindValue(k)); for (int k = 0; k < 8;)
session().setFeature("autoExtract", bindValue(k+1)); {
session().setFeature("autoBind", bindValue(k));
session().setFeature("autoExtract", bindValue(k+1));
recreatePersonTable(); recreatePersonTable();
typedef Tuple<std::string, std::string, std::string, int> Person; typedef Tuple<std::string, std::string, std::string, int> Person;
std::vector<Person> people; std::vector<Person> people;
people.push_back(Person("Simpson", "Homer", "Springfield", 42)); people.push_back(Person("Simpson", "Homer", "Springfield", 42));
people.push_back(Person("Simpson", "Bart", "Springfield", 12)); people.push_back(Person("Simpson", "Bart", "Springfield", 12));
people.push_back(Person("Simpson", "Lisa", "Springfield", 10)); people.push_back(Person("Simpson", "Lisa", "Springfield", 10));
session() << "INSERT INTO Person VALUES (?, ?, ?, ?)", use(people), now; 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"); 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() void ODBCSQLServerTest::testStoredProcedureAny()
{ {
for (int k = 0; k < 8;) try
{ {
session().setFeature("autoBind", bindValue(k)); for (int k = 0; k < 8;)
session().setFeature("autoExtract", bindValue(k+1)); {
session().setFeature("autoBind", bindValue(k));
session().setFeature("autoExtract", bindValue(k+1));
Any i = 2; Any i = 2;
Any j = 0; Any j = 0;
session() << "CREATE PROCEDURE storedProcedure(@inParam int, @outParam int OUTPUT) AS " dropObject("PROCEDURE", "storedProcedure");
"BEGIN " session() << "CREATE PROCEDURE storedProcedure(@inParam int, @outParam int OUTPUT) AS "
"SET @outParam = @inParam*@inParam; " "BEGIN "
"END;" "SET @outParam = @inParam*@inParam; "
, now; "END;"
, now;
session() << "{call storedProcedure(?, ?)}", in(i), out(j), now; session() << "{call storedProcedure(?, ?)}", in(i), out(j), now;
assertTrue (4 == AnyCast<int>(j)); assertTrue (4 == AnyCast<int>(j));
session() << "DROP PROCEDURE storedProcedure;", now;
session() << "CREATE PROCEDURE storedProcedure(@ioParam int OUTPUT) AS " dropObject("PROCEDURE", "storedProcedure");
"BEGIN " session() << "CREATE PROCEDURE storedProcedure(@ioParam int OUTPUT) AS "
"SET @ioParam = @ioParam*@ioParam; " "BEGIN "
"END;" "SET @ioParam = @ioParam*@ioParam; "
, now; "END;"
, now;
i = 2; i = 2;
session() << "{call storedProcedure(?)}", io(i), now; session() << "{call storedProcedure(?)}", io(i), now;
assertTrue (4 == AnyCast<int>(i)); assertTrue (4 == AnyCast<int>(i));
dropObject("PROCEDURE", "storedProcedure");
k += 2;
}
dropObject("PROCEDURE", "storedProcedure"); 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() 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 i = 2;
DynamicAny j = 0; DynamicAny j = 0;
session() << "CREATE PROCEDURE storedProcedure(@inParam int, @outParam int OUTPUT) AS " dropObject("PROCEDURE", "storedProcedure");
"BEGIN " session() << "CREATE PROCEDURE storedProcedure(@inParam int, @outParam int OUTPUT) AS "
"SET @outParam = @inParam*@inParam; " "BEGIN "
"END;" "SET @outParam = @inParam*@inParam; "
, now; "END;"
, now;
session() << "{call storedProcedure(?, ?)}", in(i), out(j), now; session() << "{call storedProcedure(?, ?)}", in(i), out(j), now;
assertTrue (4 == j); assertTrue (4 == j);
session() << "DROP PROCEDURE storedProcedure;", now;
session() << "CREATE PROCEDURE storedProcedure(@ioParam int OUTPUT) AS " dropObject("PROCEDURE", "storedProcedure");
"BEGIN " session() << "CREATE PROCEDURE storedProcedure(@ioParam int OUTPUT) AS "
"SET @ioParam = @ioParam*@ioParam; " "BEGIN "
"END;" "SET @ioParam = @ioParam*@ioParam; "
, now; "END;"
, now;
i = 2; i = 2;
session() << "{call storedProcedure(?)}", io(i), now; session() << "{call storedProcedure(?)}", io(i), now;
assertTrue (4 == i); assertTrue (4 == i);
k += 2;
}
dropObject("PROCEDURE", "storedProcedure"); 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<int, int> params(1, 2);
assertTrue (1 == params.get<0>());
assertTrue (2 == params.get<1>());
result = 0;
session() << "{? = call storedProcedureReturn(?, ?)}", out(result), io(params), now;
assertTrue (1 == params.get<1>());
assertTrue (2 == params.get<0>());
assertTrue (3 == result);
k += 2;
}
dropObject("PROCEDURE", "storedProcedureReturn");
}
catch (ConnectionException& ce) { std::cout << ce.toString() << std::endl; fail("testStoredProcedureReturn()"); }
catch (StatementException& se) { std::cout << se.toString() << std::endl; fail("testStoredProcedureReturn()"); }
} }
void ODBCSQLServerTest::testStoredFunction() void ODBCSQLServerTest::testStoredFunction()
{ {
for (int k = 0; k < 8;) try
{ {
session().setFeature("autoBind", bindValue(k)); for (int k = 0; k < 8;)
session().setFeature("autoExtract", bindValue(k+1)); {
session().setFeature("autoBind", bindValue(k));
session().setFeature("autoExtract", bindValue(k + 1));
dropObject("PROCEDURE", "storedFunction"); dropObject("FUNCTION", "storedFunction");
session() << "CREATE PROCEDURE storedFunction AS " session() << "CREATE FUNCTION storedFunction() "
"BEGIN " " RETURNS int AS "
"DECLARE @retVal int;" "BEGIN "
"SET @retVal = -1;" " DECLARE @retVal int;"
"RETURN @retVal;" " SET @retVal = -1;"
"END;" " RETURN @retVal;"
, now; "END;"
, now;
int i = 0; int i = 0;
session() << "{? = call storedFunction}", out(i), now; session() << "{? = call dbo.storedFunction}", out(i), now;
assertTrue (-1 == i); assertTrue(-1 == i);
dropObject("PROCEDURE", "storedFunction");
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 " i = 2;
"BEGIN " int result = 0;
"RETURN @inParam*@inParam;" session() << "{? = call dbo.storedFunction(?)}", out(result), in(i), now;
"END;" assertTrue(4 == result);
, now; result = 0;
session() << "SELECT dbo.storedFunction(?)", into(result), in(i), now;
assertTrue(4 == result);
i = 2; k += 2;
int result = 0; }
session() << "{? = call storedFunction(?)}", out(result), in(i), now; dropObject("FUNCTION", "storedFunction");
assertTrue (4 == result);
dropObject("PROCEDURE", "storedFunction");
session() << "CREATE PROCEDURE storedFunction(@inParam int, @outParam int OUTPUT) AS "
"BEGIN "
"SET @outParam = @inParam*@inParam;"
"RETURN @outParam;"
"END"
, now;
i = 2;
int j = 0;
result = 0;
session() << "{? = call storedFunction(?, ?)}", out(result), in(i), out(j), now;
assertTrue (4 == j);
assertTrue (j == result);
dropObject("PROCEDURE", "storedFunction");
session() << "CREATE PROCEDURE storedFunction(@param1 int OUTPUT,@param2 int OUTPUT) AS "
"BEGIN "
"DECLARE @temp int; "
"SET @temp = @param1;"
"SET @param1 = @param2;"
"SET @param2 = @temp;"
"RETURN @param1 + @param2; "
"END"
, now;
i = 1;
j = 2;
result = 0;
session() << "{? = call storedFunction(?, ?)}", out(result), io(i), io(j), now;
assertTrue (1 == j);
assertTrue (2 == i);
assertTrue (3 == result);
Tuple<int, int> params(1, 2);
assertTrue (1 == params.get<0>());
assertTrue (2 == params.get<1>());
result = 0;
session() << "{? = call storedFunction(?, ?)}", out(result), io(params), now;
assertTrue (1 == params.get<1>());
assertTrue (2 == params.get<0>());
assertTrue (3 == result);
dropObject("PROCEDURE", "storedFunction");
k += 2;
} }
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) catch (StatementException& ex)
{ {
bool ignoreError = false;
const StatementDiagnostics::FieldVec& flds = ex.diagnostics().fields(); const StatementDiagnostics::FieldVec& flds = ex.diagnostics().fields();
StatementDiagnostics::Iterator it = flds.begin(); StatementDiagnostics::Iterator it = flds.begin();
for (; it != flds.end(); ++it) for (; it != flds.end(); ++it)
{ {
if (3701 == it->_nativeError)//(table does not exist) if (3701 == it->_nativeError) // (does not exist)
{ return;
ignoreError = true;
break;
}
} }
if (!ignoreError) throw; throw;
} }
} }
@ -725,13 +834,13 @@ void ODBCSQLServerTest::recreateLogTable()
try try
{ {
std::string sql = "CREATE TABLE %s " std::string sql = "CREATE TABLE %s "
"(Source VARCHAR(max)," "(Source VARCHAR(256),"
"Name VARCHAR(max)," "Name VARCHAR(256),"
"ProcessId INTEGER," "ProcessId INTEGER,"
"Thread VARCHAR(max), " "Thread VARCHAR(256), "
"ThreadId INTEGER," "ThreadId INTEGER,"
"Priority INTEGER," "Priority INTEGER,"
"Text VARCHAR(max)," "Text VARCHAR(1024),"
"DateTime DATETIME)"; "DateTime DATETIME)";
session() << sql, "T_POCO_LOG", now; session() << sql, "T_POCO_LOG", now;
@ -779,6 +888,9 @@ CppUnit::Test* ODBCSQLServerTest::suite()
CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCSQLServerTest"); CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCSQLServerTest");
CppUnit_addTest(pSuite, ODBCSQLServerTest, testBareboneODBC); 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, testZeroRows);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testSimpleAccess); CppUnit_addTest(pSuite, ODBCSQLServerTest, testSimpleAccess);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testComplexType); CppUnit_addTest(pSuite, ODBCSQLServerTest, testComplexType);
@ -828,6 +940,7 @@ CppUnit::Test* ODBCSQLServerTest::suite()
CppUnit_addTest(pSuite, ODBCSQLServerTest, testBLOB); CppUnit_addTest(pSuite, ODBCSQLServerTest, testBLOB);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testBLOBContainer); CppUnit_addTest(pSuite, ODBCSQLServerTest, testBLOBContainer);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testBLOBStmt); CppUnit_addTest(pSuite, ODBCSQLServerTest, testBLOBStmt);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testRecordSet);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testDateTime); CppUnit_addTest(pSuite, ODBCSQLServerTest, testDateTime);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testFloat); CppUnit_addTest(pSuite, ODBCSQLServerTest, testFloat);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testDouble); CppUnit_addTest(pSuite, ODBCSQLServerTest, testDouble);
@ -838,6 +951,7 @@ CppUnit::Test* ODBCSQLServerTest::suite()
CppUnit_addTest(pSuite, ODBCSQLServerTest, testCursorStoredProcedure); CppUnit_addTest(pSuite, ODBCSQLServerTest, testCursorStoredProcedure);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testStoredProcedureAny); CppUnit_addTest(pSuite, ODBCSQLServerTest, testStoredProcedureAny);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testStoredProcedureDynamicAny); CppUnit_addTest(pSuite, ODBCSQLServerTest, testStoredProcedureDynamicAny);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testStoredProcedureReturn);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testStoredFunction); CppUnit_addTest(pSuite, ODBCSQLServerTest, testStoredFunction);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testInternalExtraction); CppUnit_addTest(pSuite, ODBCSQLServerTest, testInternalExtraction);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testFilter); CppUnit_addTest(pSuite, ODBCSQLServerTest, testFilter);

View File

@ -26,13 +26,14 @@ class ODBCSQLServerTest: public ODBCTest
/// SQLServer ODBC test class /// SQLServer ODBC test class
/// Tested: /// Tested:
/// ///
/// Driver | DB | OS /// Driver name | Driver version | DB version | OS
/// --------------------+-----------------------------------+------------------------------------------ /// ------------------------------------+-------------------+-----------------------+------------------------------------------
/// 2000.86.1830.00 | SQL Server Express 9.0.2047 | MS Windows XP Professional x64 v.2003/SP1 /// SQL Server Express 9.0.2047 | 2000.86.1830.00 | | 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 /// SQL Server Express 9.0.2047 | 2005.90.2047.00 | | 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 /// 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: public:
ODBCSQLServerTest(const std::string& name); ODBCSQLServerTest(const std::string& name);
@ -51,6 +52,7 @@ public:
void testStoredProcedureAny(); void testStoredProcedureAny();
void testStoredProcedureDynamicAny(); void testStoredProcedureDynamicAny();
void testStoredProcedureReturn();
void testStoredFunction(); void testStoredFunction();
static CppUnit::Test* suite(); static CppUnit::Test* suite();

View File

@ -323,6 +323,9 @@ CppUnit::Test* ODBCSQLiteTest::suite()
CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCSQLiteTest"); CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCSQLiteTest");
CppUnit_addTest(pSuite, ODBCSQLiteTest, testBareboneODBC); 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, testZeroRows);
CppUnit_addTest(pSuite, ODBCSQLiteTest, testSimpleAccess); CppUnit_addTest(pSuite, ODBCSQLiteTest, testSimpleAccess);
CppUnit_addTest(pSuite, ODBCSQLiteTest, testComplexType); CppUnit_addTest(pSuite, ODBCSQLiteTest, testComplexType);

View File

@ -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() void ODBCTest::testZeroRows()
{ {
if (!_pSession) fail ("Test not available."); 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() void ODBCTest::testDateTime()
{ {
if (!_pSession) fail ("Test not available."); if (!_pSession) fail ("Test not available.");
@ -1368,7 +1401,7 @@ ODBCTest::SessionPtr ODBCTest::init(const std::string& driver,
try 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); SessionPtr ptr = new Session(Poco::Data::ODBC::Connector::KEY, dbConnString, 5);
if (!dbEncoding.empty()) if (!dbEncoding.empty())
ptr->setProperty("dbEncoding", dbEncoding); ptr->setProperty("dbEncoding", dbEncoding);

View File

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

View File

@ -29,6 +29,8 @@
#include "Poco/Data/Date.h" #include "Poco/Data/Date.h"
#include "Poco/Data/Time.h" #include "Poco/Data/Time.h"
#include "Poco/Data/LOB.h" #include "Poco/Data/LOB.h"
#include "Poco/Data/Session.h"
#include "Poco/Data/SessionPool.h"
#include "Poco/Data/StatementImpl.h" #include "Poco/Data/StatementImpl.h"
#include "Poco/Data/RecordSet.h" #include "Poco/Data/RecordSet.h"
#include "Poco/Data/RowIterator.h" #include "Poco/Data/RowIterator.h"
@ -54,6 +56,7 @@
using namespace Poco::Data::Keywords; using namespace Poco::Data::Keywords;
using Poco::Data::Session; using Poco::Data::Session;
using Poco::Data::SessionPool;
using Poco::Data::Statement; using Poco::Data::Statement;
using Poco::Data::RecordSet; using Poco::Data::RecordSet;
using Poco::Data::Column; using Poco::Data::Column;
@ -73,6 +76,7 @@ using Poco::Data::ODBC::Preparator;
using Poco::Data::ODBC::ConnectionException; using Poco::Data::ODBC::ConnectionException;
using Poco::Data::ODBC::StatementException; using Poco::Data::ODBC::StatementException;
using Poco::Data::ODBC::DataTruncatedException; using Poco::Data::ODBC::DataTruncatedException;
using Poco::Data::ODBC::ConnectionError;
using Poco::Data::ODBC::StatementError; using Poco::Data::ODBC::StatementError;
using Poco::format; using Poco::format;
using Poco::Tuple; using Poco::Tuple;
@ -354,13 +358,13 @@ void SQLExecutor::bareboneODBCTest(const std::string& dbConnString,
assertTrue (SQL_SUCCEEDED(rc) || SQL_NO_DATA == rc); assertTrue (SQL_SUCCEEDED(rc) || SQL_NO_DATA == rc);
SQLULEN dateTimeColSize = 0; SQLULEN dateTimeColSize = 0;
SQLSMALLINT dateTimeDecDigits = 0; SQLSMALLINT dateTimeDecDigits = -1;
if (SQL_SUCCEEDED(rc)) if (SQL_SUCCEEDED(rc))
{ {
SQLLEN ind = 0; SQLLEN ind = 0;
rc = SQLGetData(hstmt, 3, SQL_C_SLONG, &dateTimeColSize, sizeof(SQLINTEGER), &ind); rc = SQLGetData(hstmt, 3, SQL_C_SLONG, &dateTimeColSize, sizeof(SQLINTEGER), &ind);
poco_odbc_check_stmt (rc, hstmt); 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); poco_odbc_check_stmt (rc, hstmt);
assertTrue (sizeof(SQL_TIMESTAMP_STRUCT) <= dateTimeColSize); assertTrue (sizeof(SQL_TIMESTAMP_STRUCT) <= dateTimeColSize);
@ -484,17 +488,25 @@ void SQLExecutor::bareboneODBCTest(const std::string& dbConnString,
0); 0);
poco_odbc_check_stmt (rc, hstmt); poco_odbc_check_stmt (rc, hstmt);
SQLSMALLINT dataType = 0; if (dateTimeColSize == 0 || dateTimeDecDigits == -1)
SQLULEN parameterSize = 0;
SQLSMALLINT decimalDigits = 0;
SQLSMALLINT nullable = 0;
rc = SQLDescribeParam(hstmt, 6, &dataType, &parameterSize, &decimalDigits, &nullable);
if (SQL_SUCCEEDED(rc))
{ {
if (parameterSize) SQLSMALLINT dataType = 0;
dateTimeColSize = parameterSize; SQLULEN parameterSize = 0;
if (decimalDigits) SQLSMALLINT decimalDigits = -1;
dateTimeDecDigits = decimalDigits; SQLSMALLINT nullable = 0;
rc = SQLDescribeParam(hstmt, 6, &dataType, &parameterSize, &decimalDigits, &nullable);
if (SQL_SUCCEEDED(rc))
{
if (parameterSize != 0 && dateTimeColSize == 0)
dateTimeColSize = parameterSize;
if (decimalDigits != -1 && dateTimeDecDigits == -1)
dateTimeDecDigits = decimalDigits;
}
else
{
std::cerr << '[' << name() << ']' << " Warning: could not get SQL_TYPE_TIMESTAMP "
"parameter description." << std::endl;
}
} }
else else
std::cerr << '[' << name() << ']' << " Warning: could not get SQL_TYPE_TIMESTAMP parameter description." << std::endl; 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) void SQLExecutor::execute(const std::string& sql)
{ {
try { session() << sql, now; } try { session() << sql, now; }
@ -947,6 +1144,314 @@ void SQLExecutor::execute(const std::string& sql)
} }
void SQLExecutor::connection(const std::string& connectString)
{
Poco::Data::ODBC::Connection c;
assertFalse (c.isConnected());
assertTrue (c.connect(connectString, 5));
assertTrue (c.isConnected());
assertTrue (c.getTimeout() == 5);
c.setTimeout(6);
assertTrue (c.getTimeout() == 6);
assertTrue (c.disconnect());
assertFalse (c.isConnected());
assertTrue (c.connect(connectString));
assertTrue (c.isConnected());
try
{
c.connect();
fail ("Connection attempt must fail when already connected");
}
catch(const Poco::InvalidAccessException&){}
assertTrue (c.disconnect());
assertFalse (c.isConnected());
try
{
c.connect();
fail ("Connection attempt with empty string must fail");
}
catch(const Poco::InvalidArgumentException&){}
}
void SQLExecutor::session(const std::string& connectString, int timeout)
{
Poco::Data::Session s(Poco::Data::ODBC::Connector::KEY, connectString, timeout);
assertTrue (s.isConnected());
s.close();
assertTrue (!s.isConnected());
s.open();
assertTrue (s.isConnected());
s.reconnect();
assertTrue (s.isConnected());
s.close();
assertTrue (!s.isConnected());
s.reconnect();
assertTrue (s.isConnected());
Poco::Any any = s.getProperty("handle");
assertTrue (typeid(SQLHDBC) == any.type());
SQLHDBC hdbc = Poco::AnyCast<SQLHDBC>(any);
assertTrue (SQL_NULL_HDBC != hdbc);
SQLRETURN rc = SQLDisconnect(hdbc);
assertTrue (!Utility::isError(rc));
assertTrue (!s.isConnected());
s.open();
assertTrue (s.isConnected());
hdbc = Poco::AnyCast<SQLHDBC>(any);
rc = SQLDisconnect(hdbc);
assertTrue (!Utility::isError(rc));
assertTrue (!s.isConnected());
s.reconnect();
assertTrue (s.isConnected());
s.close();
assertTrue (!s.isConnected());
s.reconnect();
assertTrue (s.isConnected());
s.reconnect();
assertTrue (s.isConnected());
}
void SQLExecutor::sessionPool(const std::string& connectString, int minSessions, int maxSessions, int idleTime, int timeout)
{
assertTrue (minSessions <= maxSessions);
SessionPool sp("ODBC", connectString, minSessions, maxSessions, idleTime, timeout);
assertEqual (0, sp.allocated());
assertEqual (maxSessions, sp.available());
Session s1 = sp.get();
assertEqual (minSessions, sp.allocated());
assertEqual (maxSessions-1, sp.available());
s1 = sp.get();
assertEqual (2, sp.allocated());
assertEqual (maxSessions-1, sp.available());
{
Session s2 = sp.get();
assertEqual (2, sp.allocated());
assertEqual (maxSessions-2, sp.available());
}
assertEqual (2, sp.allocated());
assertEqual (maxSessions-1, sp.available());
Thread::sleep(idleTime + 500);
assertEqual (maxSessions-minSessions, sp.available());
try
{
sp.setFeature("autoBind", true);
fail("SessionPool must throw on setFeature after the first session was created.");
}
catch(const Poco::InvalidAccessException&) {}
try
{
sp.setProperty("handle", SQL_NULL_HDBC);
fail("SessionPool must throw on setProperty after the first session was created.");
}
catch(const Poco::InvalidAccessException&) {}
std::vector<Session> sessions;
for (int i = 0; i < maxSessions-minSessions; ++i)
{
sessions.push_back(sp.get());
}
try
{
Session s = sp.get();
fail("SessionPool must throw when no sesions available.");
}
catch(const Poco::Data::SessionPoolExhaustedException&) {}
sp.shutdown();
try
{
Session s = sp.get();
fail("SessionPool that was shut down must throw on get.");
}
catch(const Poco::InvalidAccessException&) {}
{
SessionPool pool("ODBC", connectString, 1, 4, 2, 10);
pool.setFeature("f1", true);
assertTrue (pool.getFeature("f1"));
try { pool.getFeature("g1"); fail ("must fail"); }
catch ( Poco::NotFoundException& ) { }
pool.setProperty("p1", 1);
assertTrue (1 == Poco::AnyCast<int>(pool.getProperty("p1")));
try { pool.getProperty("r1"); fail ("must fail"); }
catch ( Poco::NotFoundException& ) { }
assertTrue (pool.capacity() == 4);
assertTrue (pool.allocated() == 0);
assertTrue (pool.idle() == 0);
assertTrue (pool.connTimeout() == 10);
assertTrue (pool.available() == 4);
assertTrue (pool.dead() == 0);
assertTrue (pool.allocated() == pool.used() + pool.idle());
Session s1(pool.get());
assertTrue (s1.getFeature("f1"));
assertTrue (1 == Poco::AnyCast<int>(s1.getProperty("p1")));
try { pool.setFeature("f1", true); fail ("must fail"); }
catch ( Poco::InvalidAccessException& ) { }
try { pool.setProperty("p1", 1); fail ("must fail"); }
catch ( Poco::InvalidAccessException& ) { }
assertTrue (pool.capacity() == 4);
assertTrue (pool.allocated() == 1);
assertTrue (pool.idle() == 0);
assertTrue (pool.connTimeout() == 10);
assertTrue (pool.available() == 3);
assertTrue (pool.dead() == 0);
assertTrue (pool.allocated() == pool.used() + pool.idle());
Session s2(pool.get("f1", false));
assertTrue (!s2.getFeature("f1"));
assertTrue (1 == Poco::AnyCast<int>(s2.getProperty("p1")));
assertTrue (pool.capacity() == 4);
assertTrue (pool.allocated() == 2);
assertTrue (pool.idle() == 0);
assertTrue (pool.connTimeout() == 10);
assertTrue (pool.available() == 2);
assertTrue (pool.dead() == 0);
assertTrue (pool.allocated() == pool.used() + pool.idle());
{
Session s3(pool.get("p1", 2));
assertTrue (s3.getFeature("f1"));
assertTrue (2 == Poco::AnyCast<int>(s3.getProperty("p1")));
assertTrue (pool.capacity() == 4);
assertTrue (pool.allocated() == 3);
assertTrue (pool.idle() == 0);
assertTrue (pool.connTimeout() == 10);
assertTrue (pool.available() == 1);
assertTrue (pool.dead() == 0);
assertTrue (pool.allocated() == pool.used() + pool.idle());
}
assertTrue (pool.capacity() == 4);
assertTrue (pool.allocated() == 3);
assertTrue (pool.idle() == 1);
assertTrue (pool.connTimeout() == 10);
assertTrue (pool.available() == 2);
assertTrue (pool.dead() == 0);
assertTrue (pool.allocated() == pool.used() + pool.idle());
Session s4(pool.get());
assertTrue (s4.getFeature("f1"));
assertTrue (1 == Poco::AnyCast<int>(s4.getProperty("p1")));
assertTrue (pool.capacity() == 4);
assertTrue (pool.allocated() == 3);
assertTrue (pool.idle() == 0);
assertTrue (pool.connTimeout() == 10);
assertTrue (pool.available() == 1);
assertTrue (pool.dead() == 0);
assertTrue (pool.allocated() == pool.used() + pool.idle());
Session s5(pool.get());
assertTrue (pool.capacity() == 4);
assertTrue (pool.allocated() == 4);
assertTrue (pool.idle() == 0);
assertTrue (pool.connTimeout() == 10);
assertTrue (pool.available() == 0);
assertTrue (pool.dead() == 0);
assertTrue (pool.allocated() == pool.used() + pool.idle());
try
{
Session s6(pool.get());
fail("pool exhausted - must throw");
}
catch (Poco::Data::SessionPoolExhaustedException&) { }
s5.close();
assertTrue (pool.capacity() == 4);
assertTrue (pool.allocated() == 4);
assertTrue (pool.idle() == 1);
assertTrue (pool.connTimeout() == 10);
assertTrue (pool.available() == 1);
assertTrue (pool.dead() == 0);
assertTrue (pool.allocated() == pool.used() + pool.idle());
try
{
s5 << "DROP TABLE IF EXISTS Test", now;
fail("session unusable - must throw");
}
catch (Poco::Data::SessionUnavailableException&) { }
s4.close();
assertTrue (pool.capacity() == 4);
assertTrue (pool.allocated() == 4);
assertTrue (pool.idle() == 2);
assertTrue (pool.connTimeout() == 10);
assertTrue (pool.available() == 2);
assertTrue (pool.dead() == 0);
assertTrue (pool.allocated() == pool.used() + pool.idle());
Thread::sleep(5000); // time to clean up idle sessions
assertTrue (pool.capacity() == 4);
assertTrue (pool.allocated() == 2);
assertTrue (pool.idle() == 0);
assertTrue (pool.connTimeout() == 10);
assertTrue (pool.available() == 2);
assertTrue (pool.dead() == 0);
assertTrue (pool.allocated() == pool.used() + pool.idle());
Session s6(pool.get());
assertTrue (pool.capacity() == 4);
assertTrue (pool.allocated() == 3);
assertTrue (pool.idle() == 0);
assertTrue (pool.connTimeout() == 10);
assertTrue (pool.available() == 1);
assertTrue (pool.dead() == 0);
assertTrue (pool.allocated() == pool.used() + pool.idle());
s6.setFeature("connected", false);
assertTrue (pool.dead() == 1);
s6.close();
assertTrue (pool.capacity() == 4);
assertTrue (pool.allocated() == 2);
assertTrue (pool.idle() == 0);
assertTrue (pool.connTimeout() == 10);
assertTrue (pool.available() == 2);
assertTrue (pool.dead() == 0);
assertTrue (pool.allocated() == pool.used() + pool.idle());
assertTrue (pool.isActive());
pool.shutdown();
assertTrue (!pool.isActive());
try
{
Session s7(pool.get());
fail("pool shut down - must throw");
}
catch (InvalidAccessException&) { }
assertTrue (pool.capacity() == 4);
assertTrue (pool.allocated() == 0);
assertTrue (pool.idle() == 0);
assertTrue (pool.connTimeout() == 10);
assertTrue (pool.available() == 0);
assertTrue (pool.dead() == 0);
assertTrue (pool.allocated() == pool.used() + pool.idle());
}
}
void SQLExecutor::zeroRows() void SQLExecutor::zeroRows()
{ {
Statement stmt = (session() << "SELECT * FROM Person WHERE 0 = 1"); Statement stmt = (session() << "SELECT * FROM Person WHERE 0 = 1");
@ -2470,6 +2975,106 @@ void SQLExecutor::blobStmt()
} }
void SQLExecutor::recordSet()
{
std::string funct = "dateTime()";
std::string lastName("lastname");
std::string firstName("firstname");
std::string address("Address");
DateTime born(1965, 6, 18, 5, 35, 1);
DateTime born2(1991, 6, 18, 14, 35, 1);
try
{
{
Statement stmt = (session() << "SELECT COUNT(*) AS row_count FROM Person", now);
RecordSet rset(stmt);
assertTrue (rset.rowCount() == 1);
assertTrue (rset["row_count"].convert<int>() == 0);
}
{
Statement stmt = (session() <<
"INSERT INTO Person VALUES (?,?,?,?)",
use(lastName), use(firstName), use(address), use(born), now);
RecordSet rset(stmt);
assertTrue (rset.rowCount() == 0);
assertTrue (rset.affectedRowCount() == 1);
}
{
Statement stmt = (session() << "SELECT COUNT(*) AS row_count FROM Person", now);
RecordSet rset(stmt);
assertTrue (rset.rowCount() == 1);
assertTrue (rset["row_count"].convert<int>() == 1);
}
{
Statement stmt = (session() << "SELECT Born FROM Person", now);
RecordSet rset(stmt);
assertTrue (rset.rowCount() == 1);
assertTrue (rset["Born"].convert<DateTime>() == born);
}
{
Statement stmt = (session() <<
"DELETE FROM Person WHERE born = ?", use(born), now);
RecordSet rset(stmt);
assertTrue (rset.rowCount() == 0);
assertTrue (rset.affectedRowCount() == 1);
}
{
Statement stmt = (session() <<
"INSERT INTO Person VALUES (?,?,?,?)",
use(lastName), use(firstName), use(address), use(born), now);
RecordSet rset(stmt);
assertTrue (rset.rowCount() == 0);
assertTrue (rset.affectedRowCount() == 1);
}
{
Statement stmt = (session() <<
"INSERT INTO Person VALUES (?,?,?,?)",
use(lastName), use(firstName), use(address), use(born2), now);
RecordSet rset(stmt);
assertTrue (rset.rowCount() == 0);
assertTrue (rset.affectedRowCount() == 1);
}
{
Statement stmt = (session() << "SELECT COUNT(*) AS row_count FROM Person", now);
RecordSet rset(stmt);
assertTrue (rset.rowCount() == 1);
assertTrue (rset["row_count"].convert<int>() == 2);
}
{
Statement stmt = (session() << "SELECT Born FROM Person ORDER BY Born DESC", now);
RecordSet rset(stmt);
assertTrue (rset.rowCount() == 2);
assertTrue (rset["Born"].convert<DateTime>() == born2);
rset.moveNext();
assertTrue (rset["Born"].convert<DateTime>() == born);
rset.moveFirst();
assertTrue (rset["Born"].convert<DateTime>() == born2);
rset.moveLast();
assertTrue (rset["Born"].convert<DateTime>() == born);
}
{
Statement stmt = (session() << "DELETE FROM Person", now);
RecordSet rset(stmt);
assertTrue (rset.rowCount() == 0);
assertTrue (rset.affectedRowCount() == 2);
}
}
catch (ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail(funct); }
catch (StatementException& se){ std::cout << se.toString() << std::endl; fail(funct); }
}
void SQLExecutor::dateTime() void SQLExecutor::dateTime()
{ {
std::string funct = "dateTime()"; std::string funct = "dateTime()";
@ -3457,22 +4062,35 @@ void SQLExecutor::sqlChannel(const std::string& connect)
try try
{ {
AutoPtr<SQLChannel> pChannel = new SQLChannel(Poco::Data::ODBC::Connector::KEY, connect, "TestSQLChannel"); AutoPtr<SQLChannel> pChannel = new SQLChannel(Poco::Data::ODBC::Connector::KEY, connect, "TestSQLChannel");
Stopwatch sw; sw.start();
while (!pChannel->isRunning())
{
Thread::sleep(10);
if (sw.elapsedSeconds() > 3)
fail ("SQLExecutor::sqlLogger(): SQLChannel timed out");
}
pChannel->setProperty("bulk", "true");
pChannel->setProperty("keep", "2 seconds"); pChannel->setProperty("keep", "2 seconds");
Message msgInf("InformationSource", "a Informational async message", Message::PRIO_INFORMATION); Message msgInf("InformationSource", "a Informational async message", Message::PRIO_INFORMATION);
pChannel->log(msgInf); pChannel->log(msgInf);
while (pChannel->logged() != 1) Thread::sleep(10);
Message msgWarn("WarningSource", "b Warning async message", Message::PRIO_WARNING); Message msgWarn("WarningSource", "b Warning async message", Message::PRIO_WARNING);
pChannel->log(msgWarn); 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); Message msgInfS("InformationSource", "c Informational sync message", Message::PRIO_INFORMATION);
pChannel->log(msgInfS); pChannel->log(msgInfS);
while (pChannel->logged() != 3) Thread::sleep(10);
Message msgWarnS("WarningSource", "d Warning sync message", Message::PRIO_WARNING); Message msgWarnS("WarningSource", "d Warning sync message", Message::PRIO_WARNING);
pChannel->log(msgWarnS); pChannel->log(msgWarnS);
while (pChannel->logged() != 4) Thread::sleep(10);
RecordSet rs(session(), "SELECT * FROM T_POCO_LOG ORDER by Text"); 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 ("InformationSource" == rs["Source"]);
assertTrue ("a Informational async message" == rs["Text"]); assertTrue ("a Informational async message" == rs["Text"]);
rs.moveNext(); rs.moveNext();
@ -3485,12 +4103,14 @@ void SQLExecutor::sqlChannel(const std::string& connect)
assertTrue ("WarningSource" == rs["Source"]); assertTrue ("WarningSource" == rs["Source"]);
assertTrue ("d Warning sync message" == rs["Text"]); 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); Message msgInfA("InformationSource", "e Informational sync message", Message::PRIO_INFORMATION);
pChannel->log(msgInfA); pChannel->log(msgInfA);
while (pChannel->logged() != 5) Thread::sleep(10);
Message msgWarnA("WarningSource", "f Warning sync message", Message::PRIO_WARNING); Message msgWarnA("WarningSource", "f Warning sync message", Message::PRIO_WARNING);
pChannel->log(msgWarnA); pChannel->log(msgWarnA);
while (pChannel->logged() != 6) Thread::sleep(10);
RecordSet rs1(session(), "SELECT * FROM T_POCO_LOG_ARCHIVE"); RecordSet rs1(session(), "SELECT * FROM T_POCO_LOG_ARCHIVE");
assertTrue (4 == rs1.rowCount()); assertTrue (4 == rs1.rowCount());
@ -3504,7 +4124,6 @@ void SQLExecutor::sqlChannel(const std::string& connect)
rs2.moveNext(); rs2.moveNext();
assertTrue ("WarningSource" == rs2["Source"]); assertTrue ("WarningSource" == rs2["Source"]);
assertTrue ("f Warning sync message" == rs2["Text"]); assertTrue ("f Warning sync message" == rs2["Text"]);
} }
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail ("sqlChannel()"); } catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail ("sqlChannel()"); }
catch(StatementException& se){ std::cout << se.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 try
{ {
Logger& root = Logger::root(); 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.setLevel(Message::PRIO_INFORMATION);
root.information("a Informational message"); root.information("a Informational message");
root.warning("b Warning message"); root.warning("b Warning message");
root.debug("Debug 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"); RecordSet rs(session(), "SELECT * FROM T_POCO_LOG ORDER by Text");
assertTrue (2 == rs.rowCount()); assertTrue (2 == rs.rowCount());
assertTrue ("TestSQLChannel" == rs["Source"]); assertTrue ("TestSQLChannel" == rs["Source"]);
@ -3531,7 +4159,6 @@ void SQLExecutor::sqlLogger(const std::string& connect)
rs.moveNext(); rs.moveNext();
assertTrue ("TestSQLChannel" == rs["Source"]); assertTrue ("TestSQLChannel" == rs["Source"]);
assertTrue ("b Warning message" == rs["Text"]); assertTrue ("b Warning message" == rs["Text"]);
root.setChannel(nullptr);
} }
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail ("sqlLogger()"); } catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail ("sqlLogger()"); }
catch(StatementException& se){ std::cout << se.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() void SQLExecutor::transactor()
{ {
std::string funct = "transaction()"; std::string funct = "transactor()";
int count = 0; int count = 0;
bool autoCommit = session().getFeature("autoCommit"); bool autoCommit = session().getFeature("autoCommit");

View File

@ -48,7 +48,7 @@
if (!SQL_SUCCEEDED(r)) \ if (!SQL_SUCCEEDED(r)) \
{ \ { \
Poco::Data::ODBC::StatementException se(h); \ Poco::Data::ODBC::StatementException se(h); \
std::cout << se.toString() << std::endl; \ std::cout << se.displayText() << std::endl; \
} \ } \
assert (SQL_SUCCEEDED(r)) assert (SQL_SUCCEEDED(r))
@ -57,7 +57,7 @@
if (!SQL_SUCCEEDED(r)) \ if (!SQL_SUCCEEDED(r)) \
{ \ { \
Poco::Data::ODBC::DescriptorException de(h); \ Poco::Data::ODBC::DescriptorException de(h); \
std::cout << de.toString() << std::endl; \ std::cout << de.displayText() << std::endl; \
} \ } \
assert (SQL_SUCCEEDED(r)) assert (SQL_SUCCEEDED(r))
@ -106,13 +106,24 @@ public:
SQLExecutor::DataExtraction extractMode, SQLExecutor::DataExtraction extractMode,
const std::string& insert = MULTI_INSERT, const std::string& insert = MULTI_INSERT,
const std::string& select = MULTI_SELECT); 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). /// (i.e. calls are not "wrapped" in PocoData framework structures).
/// The purpose of the functions is to verify that a driver behaves /// The purpose of the functions is to verify that a driver behaves
/// correctly as well as to determine its capabilities /// correctly as well as to determine its capabilities
/// (e.g. SQLGetData() restrictions relaxation policy, if any). /// (e.g. SQLGetData() restrictions relaxation policy, if any).
/// If these test pass, subsequent tests failures are likely ours. /// 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 zeroRows();
void simpleAccess(); void simpleAccess();
void complexType(); void complexType();
@ -463,6 +474,7 @@ public:
} }
void blobStmt(); void blobStmt();
void recordSet();
void dateTime(); void dateTime();
void date(); void date();

View File

@ -257,11 +257,6 @@ Poco::Any SessionImpl::getTransactionType(const std::string& prop) const
void SessionImpl::autoCommit(const std::string&, bool) 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.");
} }

View File

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

View File

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

View File

@ -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) void setFeature(const std::string& name, bool state)
/// Looks a feature up in the features map /// Looks a feature up in the features map
/// and calls the feature's setter, if there is one. /// and calls the feature's setter, if there is one.
@ -145,6 +155,16 @@ public:
else throw NotSupportedException(name); 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) void setProperty(const std::string& name, const Poco::Any& value)
/// Looks a property up in the properties map /// Looks a property up in the properties map
/// and calls the property's setter, if there is one. /// and calls the property's setter, if there is one.

View File

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

View File

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

View File

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

View File

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

View File

@ -22,18 +22,24 @@
#include "Poco/Data/Connector.h" #include "Poco/Data/Connector.h"
#include "Poco/Data/Session.h" #include "Poco/Data/Session.h"
#include "Poco/Data/Statement.h" #include "Poco/Data/Statement.h"
#include "Poco/Logger.h"
#include "Poco/Data/ArchiveStrategy.h" #include "Poco/Data/ArchiveStrategy.h"
#include "Poco/Channel.h" #include "Poco/Channel.h"
#include "Poco/FileChannel.h"
#include "Poco/Message.h" #include "Poco/Message.h"
#include "Poco/AutoPtr.h" #include "Poco/AutoPtr.h"
#include "Poco/String.h" #include "Poco/String.h"
#include "Poco/NotificationQueue.h"
#include "Poco/Thread.h"
#include "Poco/Mutex.h"
#include <atomic>
namespace Poco { namespace Poco {
namespace Data { 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. /// This Channel implements logging to a SQL database.
/// The channel is dependent on the schema. The DDL for /// The channel is dependent on the schema. The DDL for
/// table creation (subject to target DDL dialect dependent /// 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. /// a risk of long blocking periods in case of remote server communication delays.
{ {
public: public:
using Ptr = Poco::AutoPtr<SQLChannel>; class LogNotification : public Poco::Notification
{
public:
using Ptr = Poco::AutoPtr<LogNotification>;
LogNotification(const Poco::Message& message) :
_message(message)
{
}
const Poco::Message& message() const
{
return _message;
}
private:
Poco::Message _message;
};
static const int DEFAULT_MIN_BATCH_SIZE = 1;
static const int DEFAULT_MAX_BATCH_SIZE = 1000;
SQLChannel(); SQLChannel();
/// Creates SQLChannel. /// Creates SQLChannel.
SQLChannel(const std::string& connector, SQLChannel(const std::string& connector,
const std::string& connect, const std::string& connect,
const std::string& name = "-"); const std::string& name = "-",
/// Creates a SQLChannel with the given connector, connect string, timeout, table and 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. /// The connector must be already registered.
void open(); void open();
/// Opens the SQLChannel. /// Opens the SQLChannel.
/// Returns true if succesful.
void close(); void close(int ms = 0);
/// Closes the SQLChannel. /// 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); void log(const Message& msg);
/// Writes the log message to the database. /// Writes the log message to the database.
@ -108,6 +145,8 @@ public:
/// the target, the previous operation must have been either completed /// the target, the previous operation must have been either completed
/// or timed out (see timeout and throw properties for details on /// or timed out (see timeout and throw properties for details on
/// how abnormal conditos are handled). /// 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. /// * timeout: Timeout (ms) to wait for previous log operation completion.
/// Values "0" and "" mean no timeout. Only valid when logging /// 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. /// Setting this property to false may result in log entries being lost.
/// True values are (case insensitive) "true", "t", "yes", "y". /// True values are (case insensitive) "true", "t", "yes", "y".
/// Anything else yields false. /// 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; std::string getProperty(const std::string& name) const;
/// Returns the value of the property with the given name. /// 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 /// Waits for the completion of the previous operation and returns
/// the result. If chanel is in synchronous mode, returns 0 immediately. /// the result. If chanel is in synchronous mode, returns 0 immediately.
size_t logged() const;
/// Returns the number of logged entries.
static void registerChannel(); static void registerChannel();
/// Registers the channel with the global LoggingFactory. /// Registers the channel with the global LoggingFactory.
@ -136,56 +196,84 @@ public:
static const std::string PROP_MAX_AGE; static const std::string PROP_MAX_AGE;
static const std::string PROP_ASYNC; static const std::string PROP_ASYNC;
static const std::string PROP_TIMEOUT; 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_THROW;
static const std::string PROP_FILE;
protected: protected:
~SQLChannel(); ~SQLChannel();
private: private:
using SessionPtr = Poco::SharedPtr<Session>; static const std::string SQL_INSERT_STMT;
using StatementPtr = Poco::SharedPtr<Statement>;
using Priority = Poco::Message::Priority;
using StrategyPtr = Poco::SharedPtr<ArchiveStrategy>;
void initLogStatement(); typedef Poco::SharedPtr<Session> SessionPtr;
/// Initiallizes the log statement. typedef Poco::SharedPtr<Statement> StatementPtr;
typedef Poco::Message::Priority Priority;
typedef Poco::SharedPtr<ArchiveStrategy> StrategyPtr;
void initArchiveStatements(); void reconnect();
/// Initiallizes the archive statement. /// Closes and opens the DB connection.
void logAsync(const Message& msg); bool processOne(int minBatch = 0);
/// Waits for previous operation completion and /// Processes one message.
/// calls logSync(). If the previous operation times out, /// If the number of acummulated messages is greater
/// and _throw is true, TimeoutException is thrown, oterwise /// than minBatch, sends logs to the destination.
/// the timeout is ignored and log entry is lost. /// Returns true if log entry was processed.
void logSync(const Message& msg); size_t execSQL();
/// Inserts the message in the target database. /// Executes the log statement.
size_t logSync();
/// Inserts entries into the target database.
bool isTrue(const std::string& value) const; bool isTrue(const std::string& value) const;
/// Returns true is value is "true", "t", "yes" or "y". /// Returns true is value is "true", "t", "yes" or "y".
/// Case insensitive. /// Case insensitive.
std::string _connector; size_t logTofile(AutoPtr<FileChannel>& pFileChannel, const std::string& fileName, bool clear = false);
std::string _connect; /// Logs cached entries to a file. Called in case DB insertions fail.
SessionPtr _pSession;
StatementPtr _pLogStatement;
std::string _name;
std::string _table;
int _timeout;
bool _throw;
bool _async;
// members for log entry cache (needed for async mode) std::string maskPwd();
std::string _source; /// Masks the password in the connection
long _pid; /// string, if detected. This is not a
std::string _thread; /// bullet-proof method; if not succesful,
long _tid; /// empty string is returned.
int _priority;
std::string _text;
DateTime _dateTime;
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<int> _minBatch;
int _maxBatch;
bool _bulk;
std::atomic<bool> _throw;
// members for log entry cache
std::vector<std::string> _source;
std::vector<long> _pid;
std::vector<std::string> _thread;
std::vector<long> _tid;
std::vector<int> _priority;
std::vector<std::string> _text;
std::vector<DateTime> _dateTime;
Poco::NotificationQueue _logQueue;
std::unique_ptr<Poco::Thread> _pDBThread;
std::atomic<bool> _reconnect;
std::atomic<bool> _running;
std::atomic<bool> _stop;
std::atomic<size_t> _logged;
StrategyPtr _pArchiveStrategy;
std::string _file;
AutoPtr<FileChannel> _pFileChannel;
Poco::Logger& _logger = Poco::Logger::get("SQLChannel");
}; };
@ -193,14 +281,6 @@ private:
// inlines // 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 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 } } // namespace Poco::Data

View File

@ -273,6 +273,9 @@ public:
/// Utility function that teturns the URI formatted from supplied /// Utility function that teturns the URI formatted from supplied
/// arguments as "connector:///connectionString". /// 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); void setFeature(const std::string& name, bool state);
/// Set the state of a feature. /// Set the state of a feature.
/// ///
@ -291,6 +294,9 @@ public:
/// Throws a NotSupportedException if the requested feature is /// Throws a NotSupportedException if the requested feature is
/// not supported by the underlying implementation. /// 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); void setProperty(const std::string& name, const Poco::Any& value);
/// Set the value of a property. /// 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) inline void Session::setFeature(const std::string& name, bool state)
{ {
_pImpl->setFeature(name, 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) inline void Session::setProperty(const std::string& name, const Poco::Any& value)
{ {
_pImpl->setProperty(name, value); _pImpl->setProperty(name, value);

View File

@ -53,6 +53,11 @@ public:
static const std::size_t CONNECTION_TIMEOUT_DEFAULT = CONNECTION_TIMEOUT_INFINITE; static const std::size_t CONNECTION_TIMEOUT_DEFAULT = CONNECTION_TIMEOUT_INFINITE;
/// Default connection/login timeout in seconds. /// 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, SessionImpl(const std::string& connectionString,
std::size_t timeout = LOGIN_TIMEOUT_DEFAULT); std::size_t timeout = LOGIN_TIMEOUT_DEFAULT);
/// Creates the SessionImpl. /// Creates the SessionImpl.
@ -141,6 +146,9 @@ public:
std::string uri() const; std::string uri() const;
/// Returns the URI for this session. /// 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; virtual void setFeature(const std::string& name, bool state) = 0;
/// Set the state of a feature. /// Set the state of a feature.
/// ///
@ -159,6 +167,9 @@ public:
/// Throws a NotSupportedException if the requested feature is /// Throws a NotSupportedException if the requested feature is
/// not supported by the underlying implementation. /// 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; virtual void setProperty(const std::string& name, const Poco::Any& value) = 0;
/// Set the value of a property. /// Set the value of a property.
/// ///

View File

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

View File

@ -369,6 +369,10 @@ public:
/// Returns the number of rows extracted so far for the data set. /// Returns the number of rows extracted so far for the data set.
/// Default value indicates current data set (if any). /// 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; std::size_t extractionCount() const;
/// Returns the number of extraction storage buffers associated /// Returns the number of extraction storage buffers associated
/// with the current data set. /// with the current data set.
@ -709,6 +713,12 @@ inline void Statement::setStorage(const std::string& storage)
} }
inline std::size_t Statement::affectedRowCount() const
{
return static_cast<std::size_t>(_pImpl->affectedRowCount());
}
inline std::size_t Statement::extractionCount() const inline std::size_t Statement::extractionCount() const
{ {
return _pImpl->extractionCount(); return _pImpl->extractionCount();

View File

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

View File

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

View File

@ -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) void PooledSessionImpl::setFeature(const std::string& name, bool state)
{ {
access()->setFeature(name, 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) void PooledSessionImpl::setProperty(const std::string& name, const Poco::Any& value)
{ {
access()->setProperty(name, value); access()->setProperty(name, value);

View File

@ -244,7 +244,8 @@ Row& RecordSet::row(std::size_t pos)
std::size_t RecordSet::rowCount() const std::size_t RecordSet::rowCount() const
{ {
poco_assert (extractions().size()); if (extractions().size() == 0) return 0;
std::size_t rc = subTotalRowCount(); std::size_t rc = subTotalRowCount();
if (!isFiltered()) return rc; 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 bool RecordSet::isAllowed(std::size_t row) const
{ {
if (!isFiltered()) return true; if (!isFiltered()) return true;

View File

@ -14,12 +14,18 @@
#include "Poco/Data/SQLChannel.h" #include "Poco/Data/SQLChannel.h"
#include "Poco/Data/SessionFactory.h" #include "Poco/Data/SessionFactory.h"
#include "Poco/Data/BulkBinding.h"
#include "Poco/DateTime.h" #include "Poco/DateTime.h"
#include "Poco/DateTimeFormatter.h"
#include "Poco/DateTimeFormat.h"
#include "Poco/LoggingFactory.h" #include "Poco/LoggingFactory.h"
#include "Poco/Instantiator.h" #include "Poco/Instantiator.h"
#include "Poco/NumberParser.h" #include "Poco/NumberParser.h"
#include "Poco/NumberFormatter.h" #include "Poco/NumberFormatter.h"
#include "Poco/Stopwatch.h"
#include "Poco/Format.h" #include "Poco/Format.h"
#include "Poco/File.h"
#include <fstream>
namespace Poco { 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_MAX_AGE("keep");
const std::string SQLChannel::PROP_ASYNC("async"); const std::string SQLChannel::PROP_ASYNC("async");
const std::string SQLChannel::PROP_TIMEOUT("timeout"); 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_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(): SQLChannel::SQLChannel():
_name("-"), _name("-"),
_table("T_POCO_LOG"), _table("T_POCO_LOG"),
_tableChanged(true),
_timeout(1000), _timeout(1000),
_throw(true), _minBatch(DEFAULT_MIN_BATCH_SIZE),
_async(true), _maxBatch(DEFAULT_MAX_BATCH_SIZE),
_bulk(true),
_throw(false),
_pid(), _pid(),
_tid(), _tid(),
_priority() _priority(),
_reconnect(false),
_running(false),
_stop(false),
_logged(0)
{ {
} }
SQLChannel::SQLChannel(const std::string& connector, SQLChannel::SQLChannel(const std::string& connector,
const std::string& connect, const std::string& connect,
const std::string& name): const std::string& name,
const std::string& table,
int timeout,
int minBatch,
int maxBatch) :
_connector(connector), _connector(connector),
_connect(connect), _connect(connect),
_name(name), _name(name),
_table("T_POCO_LOG"), _table(table),
_timeout(1000), _tableChanged(true),
_throw(true), _timeout(timeout),
_async(true), _minBatch(minBatch),
_maxBatch(maxBatch),
_bulk(false),
_throw(false),
_pid(), _pid(),
_tid(), _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 try
{ {
close(); stop();
close(_timeout);
wait();
if (_pFileChannel)
_pFileChannel->close();
} }
catch (...) catch (...)
{ {
@ -84,70 +122,177 @@ SQLChannel::~SQLChannel()
} }
void SQLChannel::open() std::string SQLChannel::maskPwd()
{ {
if (_connector.empty() || _connect.empty()) std::string displayConnect = _connect;
throw IllegalStateException("Connector and connect string must be non-empty."); Poco::istring is1(displayConnect.c_str());
Poco::istring is2("pwd=");
_pSession = new Session(_connector, _connect); std::size_t pos1 = Poco::isubstr(is1, is2);
initLogStatement(); 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) void SQLChannel::log(const Message& msg)
{ {
if (_async) logAsync(msg); _logQueue.enqueueNotification(new LogNotification(msg));
else logSync(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 try
{ {
_pLogStatement->execute(); return execSQL();
} }
catch (Exception&) catch (Exception&)
{ {
if (_throw) throw; 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<LogNotification>();
if (pLN)
{
const Message& msg = pLN->message();
_source.push_back(msg.getSource());
if (_source.back().empty()) _source.back() = _name;
Poco::replaceInPlace(_source.back(), "'", "''");
_pid.push_back(msg.getPid());
_thread.push_back(msg.getThread());
Poco::replaceInPlace(_thread.back(), "'", "''");
_tid.push_back(msg.getTid());
_priority.push_back(msg.getPriority());
_text.push_back(msg.getText());
Poco::replaceInPlace(_text.back(), "'", "''");
_dateTime.push_back(msg.getTime());
}
ret = true;
}
if (_source.size() >= _minBatch) logSync();
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) void SQLChannel::setProperty(const std::string& name, const std::string& value)
{ {
Poco::FastMutex::ScopedLock l(_mutex);
if (name == PROP_NAME) if (name == PROP_NAME)
{ {
_name = value; _name = value;
@ -156,17 +301,19 @@ void SQLChannel::setProperty(const std::string& name, const std::string& value)
else if (name == PROP_CONNECTOR) else if (name == PROP_CONNECTOR)
{ {
_connector = value; _connector = value;
close(); open(); reconnect();
} }
else if (name == PROP_CONNECT) else if (name == PROP_CONNECT)
{ {
_connect = value; _connect = value;
close(); open(); reconnect();
} }
else if (name == PROP_TABLE) else if (name == PROP_TABLE)
{ {
_table = value; _table = value;
initLogStatement(); if (_pArchiveStrategy)
_pArchiveStrategy->setSource(value);
_tableChanged = true;
} }
else if (name == PROP_ARCHIVE_TABLE) else if (name == PROP_ARCHIVE_TABLE)
{ {
@ -180,7 +327,9 @@ void SQLChannel::setProperty(const std::string& name, const std::string& value)
} }
else 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) else if (name == PROP_MAX_AGE)
@ -195,15 +344,14 @@ void SQLChannel::setProperty(const std::string& name, const std::string& value)
} }
else else
{ {
ArchiveByAgeStrategy* p = new ArchiveByAgeStrategy(_connector, _connect, _table); std::string destination = ArchiveByAgeStrategy::DEFAULT_ARCHIVE_DESTINATION;
p->setThreshold(value); if (_pArchiveStrategy) destination = _pArchiveStrategy->getDestination();
_pArchiveStrategy = p; _pArchiveStrategy = new ArchiveByAgeStrategy(_connector, _connect, _table, destination, value);
} }
} }
else if (name == PROP_ASYNC) else if (name == PROP_ASYNC)
{ {
_async = isTrue(value); // no-op
initLogStatement();
} }
else if (name == PROP_TIMEOUT) else if (name == PROP_TIMEOUT)
{ {
@ -212,10 +360,32 @@ void SQLChannel::setProperty(const std::string& name, const std::string& value)
else else
_timeout = NumberParser::parse(value); _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) else if (name == PROP_THROW)
{ {
_throw = isTrue(value); _throw = isTrue(value);
} }
else if (name == PROP_FILE)
{
_file = value;
}
else else
{ {
Channel::setProperty(name, value); 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 std::string SQLChannel::getProperty(const std::string& name) const
{ {
Poco::FastMutex::ScopedLock l(_mutex);
if (name == PROP_NAME) if (name == PROP_NAME)
{ {
if (_name != "-") return _name; if (_name != "-") return _name;
@ -254,11 +426,28 @@ std::string SQLChannel::getProperty(const std::string& name) const
{ {
return NumberFormatter::format(_timeout); 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) else if (name == PROP_THROW)
{ {
if (_throw) return "true"; if (_throw) return "true";
else return "false"; else return "false";
} }
else if (name == PROP_FILE)
{
return _file;
}
else else
{ {
return Channel::getProperty(name); return Channel::getProperty(name);
@ -266,23 +455,186 @@ std::string SQLChannel::getProperty(const std::string& name) const
} }
void SQLChannel::initLogStatement() size_t SQLChannel::logTofile(AutoPtr<FileChannel>& pFileChannel, const std::string& fileName, bool clear)
{ {
_pLogStatement = new Statement(*_pSession); static std::vector<std::string> names;
if (names.size() != _source.size())
names.resize(_source.size(), Poco::replace(_name, "'", "''"));
std::string sql; std::size_t n = 0;
Poco::format(sql, "INSERT INTO %s VALUES (?,?,?,?,?,?,?,?)", _table);
*_pLogStatement << sql,
use(_source),
use(_name),
use(_pid),
use(_thread),
use(_tid),
use(_priority),
use(_text),
use(_dateTime);
if (_async) _pLogStatement->setAsync(); if (!pFileChannel) pFileChannel = new FileChannel(fileName);
if (pFileChannel)
{
std::string sql;
Poco::format(sql, SQL_INSERT_STMT, _table, std::string());
std::stringstream os;
os << sql << '\n';
auto it = _source.begin();
auto end = _source.end();
int idx = 0, batch = 0;
for (; it != end; ++idx)
{
std::string dt = Poco::DateTimeFormatter::format(_dateTime[idx], "%Y-%m-%d %H:%M:%S.%i");
os << "('" << *it << "','" <<
names[idx] << "'," <<
_pid[idx] << ",'" <<
_thread[idx] << "'," <<
_tid[idx] << ',' <<
_priority[idx] << ",'" <<
_text[idx] << "','" <<
dt << "')";
if (++batch == _maxBatch)
{
os << ";\n";
Message msg(_source[0], os.str(), Message::PRIO_ERROR);
pFileChannel->log(msg);
os.str(""); sql.clear();
Poco::format(sql, SQL_INSERT_STMT, _table, std::string());
batch = 0;
}
if (++it == end)
{
os << ";\n";
break;
}
os << ",\n";
}
Message msg(_source[0], os.str(), Message::PRIO_ERROR);
pFileChannel->log(msg);
n = _source.size();
if (clear && n)
{
_source.clear();
_pid.clear();
_thread.clear();
_tid.clear();
_priority.clear();
_text.clear();
_dateTime.clear();
}
}
return n;
}
size_t SQLChannel::execSQL()
{
static std::vector<std::string> names;
if (names.size() != _source.size())
names.resize(_source.size(), Poco::replace(_name, "'", "''"));
static std::string placeholders = "(?,?,?,?,?,?,?,?)";
Poco::FastMutex::ScopedLock l(_mutex);
if (_tableChanged)
{
Poco::format(_sql, SQL_INSERT_STMT, _table, placeholders);
_tableChanged = false;
}
if (!_pSession || !_pSession->isConnected()) open();
if (_pArchiveStrategy) _pArchiveStrategy->archive();
size_t n = 0;
if (_pSession)
{
try
{
if (_bulk)
{
try
{
(*_pSession) << _sql,
use(_source, bulk),
use(names, bulk),
use(_pid, bulk),
use(_thread, bulk),
use(_tid, bulk),
use(_priority, bulk),
use(_text, bulk),
use(_dateTime, bulk), now;
}
// most likely bulk mode not supported,
// log and try again
catch (Poco::InvalidAccessException& ex)
{
_logger.log(ex);
(*_pSession) << _sql,
use(_source),
use(names),
use(_pid),
use(_thread),
use(_tid),
use(_priority),
use(_text),
use(_dateTime), now;
_bulk = false;
}
}
else
{
(*_pSession) << _sql,
use(_source),
use(names),
use(_pid),
use(_thread),
use(_tid),
use(_priority),
use(_text),
use(_dateTime), now;
}
n = _source.size();
}
catch (Poco::Exception& ex)
{
_logger.error(ex.displayText());
if (!_file.empty())
n = logTofile(_pFileChannel, _file);
close(_timeout);
_reconnect = true;
}
catch (std::exception& ex)
{
_logger.error(ex.what());
if (!_file.empty())
n = logTofile(_pFileChannel, _file);
close(_timeout);
_reconnect = true;
}
}
else
{
if (!_file.empty())
n = logTofile(_pFileChannel, _file);
}
if (n)
{
_logged += n;
_source.clear();
_pid.clear();
_thread.clear();
_tid.clear();
_priority.clear();
_text.clear();
_dateTime.clear();
}
return n;
}
std::size_t SQLChannel::wait(int ms)
{
Stopwatch sw;
sw.start();
int processed = _logQueue.size();
while (_logQueue.size())
{
Thread::sleep(10);
if (ms && sw.elapsed() * 1000 > ms)
break;
}
return processed - _logQueue.size();
} }

View File

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

View File

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

View File

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