diff --git a/Data/MySQL/include/Poco/Data/MySQL/MySQLException.h b/Data/MySQL/include/Poco/Data/MySQL/MySQLException.h index 2d51db997..9a0a754e7 100644 --- a/Data/MySQL/include/Poco/Data/MySQL/MySQLException.h +++ b/Data/MySQL/include/Poco/Data/MySQL/MySQLException.h @@ -44,6 +44,9 @@ public: MySQLException(const MySQLException& exc); /// Creates MySQLException. + MySQLException(const std::string& msg, int code); + /// Creates MySQLException. + ~MySQLException() noexcept; /// Destroys MySQLexception. diff --git a/Data/MySQL/include/Poco/Data/MySQL/SessionHandle.h b/Data/MySQL/include/Poco/Data/MySQL/SessionHandle.h index f313f46b3..3417a6cf6 100644 --- a/Data/MySQL/include/Poco/Data/MySQL/SessionHandle.h +++ b/Data/MySQL/include/Poco/Data/MySQL/SessionHandle.h @@ -70,6 +70,9 @@ public: void reset(); /// Reset connection with dababase and clears session state, but without disconnecting + bool ping(); + /// Checks if the connection is alive. + operator MYSQL* (); private: diff --git a/Data/MySQL/include/Poco/Data/MySQL/SessionImpl.h b/Data/MySQL/include/Poco/Data/MySQL/SessionImpl.h index 570794efe..31192db48 100644 --- a/Data/MySQL/include/Poco/Data/MySQL/SessionImpl.h +++ b/Data/MySQL/include/Poco/Data/MySQL/SessionImpl.h @@ -54,10 +54,10 @@ public: /// for compress and auto-reconnect correct values are true/false /// for port - numeric in decimal notation /// - + ~SessionImpl(); /// Destroys the SessionImpl. - + Poco::SharedPtr createStatementImpl(); /// Returns an MySQL StatementImpl @@ -69,10 +69,22 @@ public: void reset(); /// Reset connection with dababase and clears session state, but without disconnecting - + bool isConnected() const; /// Returns true if connected, false otherwise. + bool isGood() const; + /// Returns true iff the database session is good. + /// For the session to be considered good: + /// - it must be connected + /// - and it's last error code must be 0, + /// or mysql_ping() must be okay. + /// + /// Furthermore, if the "failIfInnoReadOnly" property + /// has been set to true, the innodb_read_only setting + /// must be false. The flag is only checked if the + /// session has a non-zero error code. + void setConnectionTimeout(std::size_t timeout); /// Sets the session connection timeout value. @@ -81,13 +93,13 @@ public: void begin(); /// Starts a transaction - + void commit(); - /// Commits and ends a transaction + /// Commits and ends a transaction void rollback(); /// Aborts a transaction - + bool canTransact() const; /// Returns true if session has transaction capabilities. @@ -107,7 +119,7 @@ public: bool isTransactionIsolation(Poco::UInt32 ti) const; /// Returns true iff the transaction isolation level corresponds /// to the supplied bitmask. - + void autoCommit(const std::string&, bool val); /// Sets autocommit property for the session. @@ -116,10 +128,24 @@ public: void setInsertId(const std::string&, const Poco::Any&); /// Try to set insert id - do nothing. - + Poco::Any getInsertId(const std::string&) const; /// Get insert id + void setFailIfInnoReadOnly(const std::string&, bool value); + /// Sets the "failIfInnoReadOnly" feature. If set, isGood() will + /// return false if the database is in read-only mode. + + bool getFailIfInnoReadOnly(const std::string&) const; + /// Returns the state of the "failIfInnoReadOnly" feature. + + void setLastError(int err); + /// Sets an error code. If a non-zero error code is set, the session + /// is considered bad. + + int getLastError() const; + /// Returns the last set error code. + SessionHandle& handle(); // Get handle @@ -158,7 +184,9 @@ private: mutable SessionHandle _handle; bool _connected; bool _inTransaction; + bool _failIfInnoReadOnly; std::size_t _timeout; + mutable int _lastError; Poco::FastMutex _mutex; }; @@ -183,6 +211,30 @@ inline Poco::Any SessionImpl::getInsertId(const std::string&) const } +inline void SessionImpl::setFailIfInnoReadOnly(const std::string&, bool value) +{ + _failIfInnoReadOnly = value; +} + + +inline bool SessionImpl::getFailIfInnoReadOnly(const std::string&) const +{ + return _failIfInnoReadOnly; +} + + +inline void SessionImpl::setLastError(int err) +{ + _lastError = err; +} + + +inline int SessionImpl::getLastError() const +{ + return _lastError; +} + + inline SessionHandle& SessionImpl::handle() { return _handle; @@ -207,12 +259,6 @@ inline bool SessionImpl::isTransactionIsolation(Poco::UInt32 ti) const } -inline bool SessionImpl::isConnected() const -{ - return _connected; -} - - inline std::size_t SessionImpl::getConnectionTimeout() const { return _timeout; diff --git a/Data/MySQL/src/MySQLException.cpp b/Data/MySQL/src/MySQLException.cpp index def02288a..68dc2f9f8 100644 --- a/Data/MySQL/src/MySQLException.cpp +++ b/Data/MySQL/src/MySQLException.cpp @@ -32,6 +32,11 @@ MySQLException::MySQLException(const MySQLException& exc) : Poco::Data::DataExce } +MySQLException::MySQLException(const std::string& msg, int code) : Poco::Data::DataException(std::string("[MySQL]: ") + msg, code) +{ +} + + MySQLException::~MySQLException() noexcept { } diff --git a/Data/MySQL/src/MySQLStatementImpl.cpp b/Data/MySQL/src/MySQLStatementImpl.cpp index 01e590040..7bc39e8ad 100644 --- a/Data/MySQL/src/MySQLStatementImpl.cpp +++ b/Data/MySQL/src/MySQLStatementImpl.cpp @@ -21,10 +21,10 @@ namespace MySQL { MySQLStatementImpl::MySQLStatementImpl(SessionImpl& h) : - Poco::Data::StatementImpl(h), - _stmt(h.handle()), + Poco::Data::StatementImpl(h), + _stmt(h.handle()), _pBinder(new Binder), - _pExtractor(new Extractor(_stmt, _metadata)), + _pExtractor(new Extractor(_stmt, _metadata)), _hasNext(NEXT_DONTKNOW) { } @@ -46,13 +46,13 @@ int MySQLStatementImpl::affectedRowCount() const return _stmt.getAffectedRowCount(); } - + const MetaColumn& MySQLStatementImpl::metaColumn(std::size_t pos) const { return _metadata.metaColumn(pos); } - + bool MySQLStatementImpl::hasNext() { if (_hasNext == NEXT_DONTKNOW) @@ -79,11 +79,11 @@ bool MySQLStatementImpl::hasNext() return false; } - + std::size_t MySQLStatementImpl::next() { if (!hasNext()) - throw StatementException("No data received"); + throw StatementException("No data received"); Poco::Data::AbstractExtractionVec::iterator it = extractions().begin(); Poco::Data::AbstractExtractionVec::iterator itEnd = extractions().end(); @@ -119,12 +119,21 @@ bool MySQLStatementImpl::canCompile() const void MySQLStatementImpl::compileImpl() { - _metadata.reset(); - _stmt.prepare(toString()); - _metadata.init(_stmt); + try + { + _metadata.reset(); + _stmt.prepare(toString()); + _metadata.init(_stmt); - if (_metadata.columnsReturned() > 0) - _stmt.bindResult(_metadata.row()); + if (_metadata.columnsReturned() > 0) + _stmt.bindResult(_metadata.row()); + } + catch (MySQLException& exc) + { + static_cast(session()).setLastError(exc.code()); + throw; + } + static_cast(session()).setLastError(0); } @@ -141,8 +150,17 @@ void MySQLStatementImpl::bindImpl() } _stmt.bindParams(_pBinder->getBindArray(), _pBinder->size()); - _stmt.execute(); + try + { + _stmt.execute(); + } + catch (MySQLException& exc) + { + static_cast(session()).setLastError(exc.code()); + throw; + } _hasNext = NEXT_DONTKNOW; + static_cast(session()).setLastError(0); } diff --git a/Data/MySQL/src/SessionHandle.cpp b/Data/MySQL/src/SessionHandle.cpp index d2c10364d..ba935cdb4 100644 --- a/Data/MySQL/src/SessionHandle.cpp +++ b/Data/MySQL/src/SessionHandle.cpp @@ -42,23 +42,23 @@ public: if (pthread_key_create(&_key, &ThreadCleanupHelper::cleanup) != 0) throw Poco::SystemException("cannot create TLS key for mysql cleanup"); } - + void init() { if (pthread_setspecific(_key, reinterpret_cast(1))) throw Poco::SystemException("cannot set TLS key for mysql cleanup"); } - + static ThreadCleanupHelper& instance() { return *_sh.get(); } - + static void cleanup(void* data) { mysql_thread_end(); } - + private: pthread_key_t _key; static Poco::SingletonHolder _sh; @@ -192,4 +192,11 @@ void SessionHandle::reset() } +bool SessionHandle::ping() +{ + int rc = mysql_ping(_pHandle); + return rc == 0; +} + + } } } // namespace Poco::Data::MySQL diff --git a/Data/MySQL/src/SessionImpl.cpp b/Data/MySQL/src/SessionImpl.cpp index 88b655ef1..09eda1dc6 100644 --- a/Data/MySQL/src/SessionImpl.cpp +++ b/Data/MySQL/src/SessionImpl.cpp @@ -49,10 +49,13 @@ SessionImpl::SessionImpl(const std::string& connectionString, std::size_t loginT _connector("MySQL"), _handle(0), _connected(false), - _inTransaction(false) + _inTransaction(false), + _failIfInnoReadOnly(false), + _lastError(0) { addProperty("insertId", &SessionImpl::setInsertId, &SessionImpl::getInsertId); setProperty("handle", static_cast(_handle)); + addFeature("failIfInnoReadOnly", &SessionImpl::setFailIfInnoReadOnly, &SessionImpl::getFailIfInnoReadOnly); open(); } @@ -129,7 +132,7 @@ void SessionImpl::open(const std::string& connect) else if (!options["auto-reconnect"].empty()) throw MySQLException("create session: specify correct auto-reconnect option (true or false) or skip it"); -#ifdef MYSQL_SECURE_AUTH +#ifdef MYSQL_SECURE_AUTH if (options["secure-auth"] == "true") _handle.options(MYSQL_SECURE_AUTH, true); else if (options["secure-auth"] == "false") @@ -266,6 +269,50 @@ void SessionImpl::reset() } +inline bool SessionImpl::isConnected() const +{ + return _connected; +} + + +bool SessionImpl::isGood() const +{ + if (_connected) + { + if (_lastError) + { + if (_failIfInnoReadOnly) + { + try + { + int ro = 0; + if (0 == getSetting("innodb_read_only", ro)) + { + _lastError = 0; + return true; + } + } + catch (...) + { + } + return false; + } + else + { + if (_handle.ping()) + { + _lastError = 0; + return true; + } + return false; + } + } + else return true; + } + else return false; +} + + void SessionImpl::close() { if (_connected) diff --git a/Data/include/Poco/Data/PooledSessionImpl.h b/Data/include/Poco/Data/PooledSessionImpl.h index e96491905..90d234633 100644 --- a/Data/include/Poco/Data/PooledSessionImpl.h +++ b/Data/include/Poco/Data/PooledSessionImpl.h @@ -52,6 +52,7 @@ public: void close(); void reset(); bool isConnected() const; + bool isGood() const; void setConnectionTimeout(std::size_t timeout); std::size_t getConnectionTimeout() const; bool canTransact() const; diff --git a/Data/include/Poco/Data/Session.h b/Data/include/Poco/Data/Session.h index 551e8d6ce..9fe700efa 100644 --- a/Data/include/Poco/Data/Session.h +++ b/Data/include/Poco/Data/Session.h @@ -218,6 +218,9 @@ public: void reconnect(); /// Closes the session and opens it. + bool isGood(); + /// Returns true iff the session is good and can be used, false otherwise. + void setLoginTimeout(std::size_t timeout); /// Sets the session login timeout value. @@ -350,6 +353,12 @@ inline void Session::reconnect() } +inline bool Session::isGood() +{ + return _pImpl->isGood(); +} + + inline void Session::setLoginTimeout(std::size_t timeout) { _pImpl->setLoginTimeout(timeout); diff --git a/Data/include/Poco/Data/SessionImpl.h b/Data/include/Poco/Data/SessionImpl.h index f86a93249..3b9625e9d 100644 --- a/Data/include/Poco/Data/SessionImpl.h +++ b/Data/include/Poco/Data/SessionImpl.h @@ -35,7 +35,7 @@ class StatementImpl; class Data_API SessionImpl: public Poco::RefCountedObject - /// Interface for Session functionality that subclasses must extend. + /// Interface for Session functionality that subclasses must extend. /// SessionImpl objects are noncopyable. { public: @@ -65,10 +65,10 @@ public: virtual void open(const std::string& connectionString = "") = 0; /// Opens the session using the supplied string. - /// Can also be used with default empty string to reconnect + /// Can also be used with default empty string to reconnect /// a disconnected session. - /// If the connection is not established within requested timeout - /// (specified in seconds), a ConnectionFailedException is thrown. + /// If the connection is not established within requested timeout + /// (specified in seconds), a ConnectionFailedException is thrown. /// Zero timout means indefinite virtual void close() = 0; @@ -77,6 +77,11 @@ public: virtual bool isConnected() const = 0; /// Returns true if session is connected, false otherwise. + virtual bool isGood() const; + /// Returns true if session is good and can be used, false otherwise. + /// + /// The default implementation returns result of isConnected(). + void setLoginTimeout(std::size_t timeout); /// Sets the session login timeout value. @@ -144,7 +149,7 @@ public: /// /// Throws a NotSupportedException if the requested feature is /// not supported by the underlying implementation. - + virtual bool getFeature(const std::string& name) = 0; /// Look up the state of a feature. /// diff --git a/Data/src/PooledSessionImpl.cpp b/Data/src/PooledSessionImpl.cpp index 2097d547c..d5f5abf68 100644 --- a/Data/src/PooledSessionImpl.cpp +++ b/Data/src/PooledSessionImpl.cpp @@ -66,6 +66,12 @@ bool PooledSessionImpl::isConnected() const } +bool PooledSessionImpl::isGood() const +{ + return access()->isGood(); +} + + void PooledSessionImpl::setConnectionTimeout(std::size_t timeout) { return access()->setConnectionTimeout(timeout); @@ -160,7 +166,7 @@ const std::string& PooledSessionImpl::connectorName() const } -void PooledSessionImpl::setFeature(const std::string& name, bool state) +void PooledSessionImpl::setFeature(const std::string& name, bool state) { access()->setFeature(name, state); } diff --git a/Data/src/SessionImpl.cpp b/Data/src/SessionImpl.cpp index a422f7516..0b7cded41 100644 --- a/Data/src/SessionImpl.cpp +++ b/Data/src/SessionImpl.cpp @@ -20,7 +20,7 @@ namespace Poco { namespace Data { -SessionImpl::SessionImpl(const std::string& connectionString, std::size_t timeout): +SessionImpl::SessionImpl(const std::string& connectionString, std::size_t timeout): _connectionString(connectionString), _loginTimeout(timeout) { @@ -49,4 +49,10 @@ void SessionImpl::setConnectionString(const std::string& connectionString) } +bool SessionImpl::isGood() const +{ + return isConnected(); +} + + } } // namespace Poco::Data diff --git a/Data/src/SessionPool.cpp b/Data/src/SessionPool.cpp index 00b519ccc..764ecb088 100644 --- a/Data/src/SessionPool.cpp +++ b/Data/src/SessionPool.cpp @@ -100,7 +100,7 @@ void SessionPool::purgeDeadSessions() SessionList::iterator it = _idleSessions.begin(); for (; it != _idleSessions.end(); ) { - if (!(*it)->session()->isConnected()) + if (!(*it)->session()->isGood()) { it = _idleSessions.erase(it); --_nSessions; @@ -139,7 +139,7 @@ int SessionPool::dead() SessionList::iterator itEnd = _activeSessions.end(); for (; it != itEnd; ++it) { - if (!(*it)->session()->isConnected()) + if (!(*it)->session()->isGood()) ++count; } @@ -233,7 +233,7 @@ void SessionPool::putBack(PooledSessionHolderPtr pHolder) SessionList::iterator it = std::find(_activeSessions.begin(), _activeSessions.end(), pHolder); if (it != _activeSessions.end()) { - if (pHolder->session()->isConnected()) + if (pHolder->session()->isGood()) { pHolder->session()->reset(); @@ -271,7 +271,7 @@ void SessionPool::onJanitorTimer(Poco::Timer&) SessionList::iterator it = _idleSessions.begin(); while (_nSessions > _minSessions && it != _idleSessions.end()) { - if ((*it)->idle() > _idleTime || !(*it)->session()->isConnected()) + if ((*it)->idle() > _idleTime || !(*it)->session()->isGood()) { try { (*it)->session()->close(); } catch (...) { }