Commit 3daef94c authored by Maciej Sobczak's avatar Maciej Sobczak

Added failover callback for Oracle and PostgreSQL.

parent a0b29bc9
//
// Copyright (C) 2015 Maciej Sobczak
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef SOCI_CALLBACKS_H_INCLUDED
#define SOCI_CALLBACKS_H_INCLUDED
namespace soci
{
class session;
// Simple callback interface for reporting failover events.
// The meaning of each operation is intended to be portable,
// but the behaviour details and parameters can be backend-specific.
class SOCI_DECL failover_callback
{
public:
// Called when the failover operation has started,
// after discovering connectivity problems.
virtual void started() {}
// Called after successful failover and creating a new connection;
// the sql parameter denotes the new connection and allows the user
// to replay any initial sequence of commands (like session configuration).
virtual void finished(session & /* sql */) {}
// Called when the attempt to reconnect failed,
// if the user code sets the retry parameter to true,
// then new connection will be attempted;
// the newTarget connection string is a hint that can be ignored
// by external means.
virtual void failed(bool & /* out */ /* retry */,
std::string & /* out */ newTarget) {}
// Called when there was a failure that prevents further failover attempts.
virtual void aborted() {}
};
} // namespace soci
#endif // SOCI_CALLBACKS_H_INCLUDED
......@@ -45,6 +45,8 @@ private:
error_category cat_;
};
struct postgresql_session_backend;
namespace details
{
......@@ -54,18 +56,21 @@ class postgresql_result
{
public:
// Creates a wrapper for the given, possibly NULL, result. The wrapper
// object takes ownership of the object and will call PQclear() on it.
explicit postgresql_result(PGresult* result = NULL)
// object takes ownership of the result object and will call PQclear() on it.
explicit postgresql_result(
postgresql_session_backend & sessionBackend,
PGresult * result)
: sessionBackend_(sessionBackend)
{
init(result);
init(result);
}
// Frees any currently stored result pointer and takes ownership of the
// given one.
void reset(PGresult* result = NULL)
{
free();
init(result);
free();
init(result);
}
// Check whether the status is PGRES_COMMAND_OK and throw an exception if
......@@ -101,7 +106,7 @@ public:
private:
void init(PGresult* result)
{
result_ = result;
result_ = result;
}
void free()
......@@ -111,6 +116,7 @@ private:
PQclear(result_);
}
postgresql_session_backend & sessionBackend_;
PGresult* result_;
SOCI_NOT_COPYABLE(postgresql_result)
......@@ -212,7 +218,6 @@ struct postgresql_vector_use_type_backend : details::vector_use_type_backend
std::vector<char *> buffers_;
};
struct postgresql_session_backend;
struct postgresql_statement_backend : details::statement_backend
{
postgresql_statement_backend(postgresql_session_backend & session,
......@@ -312,6 +317,8 @@ struct postgresql_session_backend : details::session_backend
~postgresql_session_backend();
void connect(connection_parameters const & parameters);
virtual void begin();
virtual void commit();
virtual void rollback();
......
......@@ -36,6 +36,7 @@ class blob_backend;
} // namespace details
class connection_pool;
class failover_callback;
class SOCI_DECL session
{
......@@ -147,6 +148,12 @@ public:
ddl_type drop_column(const std::string & tableName,
const std::string & columnName);
// Sets the failover callback object.
void set_failover_callback(failover_callback & callback)
{
backEnd_->set_failover_callback(callback, *this);
}
// for diagnostics and advanced users
// (downcast it to expected back-end session class)
details::session_backend * get_backend() { return backEnd_; }
......
......@@ -29,6 +29,7 @@ enum data_type
enum indicator { i_ok, i_null, i_truncated };
class session;
class failover_callback;
namespace details
{
......@@ -213,7 +214,7 @@ private:
class session_backend
{
public:
session_backend() {}
session_backend() : failoverCallback_(NULL), session_(NULL) {}
virtual ~session_backend() {}
virtual void begin() = 0;
......@@ -372,12 +373,21 @@ public:
" references " + refTableName + " (" + refColumnNames + ")";
}
void set_failover_callback(failover_callback & callback, session & sql)
{
failoverCallback_ = &callback;
session_ = &sql;
}
virtual std::string get_backend_name() const = 0;
virtual statement_backend* make_statement_backend() = 0;
virtual rowid_backend* make_rowid_backend() = 0;
virtual blob_backend* make_blob_backend() = 0;
failover_callback * failoverCallback_;
session * session_;
private:
SOCI_NOT_COPYABLE(session_backend)
};
......
......@@ -7,6 +7,7 @@
#define SOCI_ORACLE_SOURCE
#include "soci/oracle/soci-oracle.h"
#include "soci/callbacks.h"
#include "error.h"
#include <cctype>
#include <cstdio>
......@@ -22,11 +23,109 @@ using namespace soci;
using namespace soci::details;
using namespace soci::details::oracle;
namespace // unnamed
{
sb4 fo_callback(void * svchp, void * envhp, void * fo_ctx,
ub4 fo_type, ub4 fo_event)
{
oracle_session_backend * backend =
static_cast<oracle_session_backend *>(fo_ctx);
failover_callback * callback = backend->failoverCallback_;
if (callback != NULL)
{
session * sql = backend->session_;
switch (fo_event)
{
case OCI_FO_BEGIN:
// failover operation was initiated
try
{
callback->started();
}
catch (...)
{
// ignore exceptions from user callbacks
}
break;
case OCI_FO_END:
// failover was successful
try
{
callback->finished(*sql);
}
catch (...)
{
// ignore exceptions from user callbacks
}
break;
case OCI_FO_ABORT:
// failover was aborted with no possibility to recovery
try
{
callback->aborted();
}
catch (...)
{
// ignore exceptions from user callbacks
}
break;
case OCI_FO_ERROR:
// failover failed, but can be retried
try
{
bool retry = false;
std::string newTarget;
callback->failed(retry, newTarget);
// newTarget is ignored, as the new target
// is selected by Oracle client configuration
if (retry)
{
return OCI_FO_RETRY;
}
}
catch (...)
{
// ignore exceptions from user callbacks
}
break;
case OCI_FO_REAUTH:
// nothing interesting
break;
default:
// ignore unknown callback types (if any)
break;
}
}
return 0;
}
} // unnamed namespace
oracle_session_backend::oracle_session_backend(std::string const & serviceName,
std::string const & userName, std::string const & password, int mode,
bool decimals_as_strings, int charset, int ncharset)
: envhp_(NULL), srvhp_(NULL), errhp_(NULL), svchp_(NULL), usrhp_(NULL)
, decimals_as_strings_(decimals_as_strings)
: envhp_(NULL), srvhp_(NULL), errhp_(NULL), svchp_(NULL), usrhp_(NULL),
decimals_as_strings_(decimals_as_strings)
{
// assume service/user/password are utf8-compatible already
const int defaultSourceCharSetId = 871;
......@@ -156,6 +255,22 @@ oracle_session_backend::oracle_session_backend(std::string const & serviceName,
throw oracle_soci_error(msg, errNum);
}
// register failover callback
OCIFocbkStruct fo;
fo.fo_ctx = this;
fo.callback_function = &fo_callback;
res = OCIAttrSet(srvhp_, static_cast<ub4>(OCI_HTYPE_SERVER),
&fo, 0, static_cast<ub4>(OCI_ATTR_FOCBK), errhp_);
if (res != OCI_SUCCESS)
{
std::string msg;
int errNum;
get_error_details(res, errhp_, msg, errNum);
clean_up();
throw oracle_soci_error(msg, errNum);
}
// create service context handle
res = OCIHandleAlloc(envhp_, reinterpret_cast<dvoid**>(&svchp_),
OCI_HTYPE_SVCCTX, 0, 0);
......
......@@ -7,6 +7,8 @@
#define SOCI_POSTGRESQL_SOURCE
#include "soci/postgresql/soci-postgresql.h"
#include "soci/callbacks.h"
#include "soci/connection-parameters.h"
#include <cstring>
using namespace soci;
......@@ -61,6 +63,8 @@ details::postgresql_result::check_for_errors(char const* errMsg) const
bool
details::postgresql_result::check_for_data(char const* errMsg) const
{
std::string msg(errMsg);
ExecStatusType const status = PQresultStatus(result_);
switch (status)
{
......@@ -72,19 +76,74 @@ details::postgresql_result::check_for_data(char const* errMsg) const
case PGRES_TUPLES_OK:
return true;
case PGRES_FATAL_ERROR:
msg += " Fatal error.";
if (PQstatus(sessionBackend_.conn_) == CONNECTION_BAD)
{
msg += " Connection failed.";
// call the failover callback, if registered
failover_callback * callback = sessionBackend_.failoverCallback_;
if (callback != NULL)
{
bool reconnected = false;
try
{
callback->started();
bool retry = false;
std::string newTarget;
callback->failed(retry, newTarget);
if (retry)
{
connection_parameters parameters("postgresql", newTarget);
sessionBackend_.clean_up();
sessionBackend_.connect(parameters);
reconnected = true;
}
}
catch (...)
{
// ignore exceptions from user callbacks
}
if (reconnected == false)
{
try
{
callback->aborted();
}
catch (...)
{
// ignore exceptions from user callbacks
}
}
}
}
break;
default:
// Some of the other status codes are not really errors but we're
// not prepared to handle them right now and shouldn't ever receive
// them so throw nevertheless
break;
}
std::string msg(errMsg);
const char* const pqError = PQresultErrorMessage(result_);
if (pqError && *pqError)
{
msg += " ";
msg += pqError;
msg += " ";
msg += pqError;
}
const char* sqlstate = PQresultErrorField(result_, PG_DIAG_SQLSTATE);
......
......@@ -30,9 +30,10 @@ namespace // unnamed
{
// helper function for hardcoded queries
void hard_exec(PGconn * conn, char const * query, char const * errMsg)
void hard_exec(postgresql_session_backend & session_backend,
PGconn * conn, char const * query, char const * errMsg)
{
postgresql_result(PQexec(conn, query)).check_for_errors(errMsg);
postgresql_result(session_backend, PQexec(conn, query)).check_for_errors(errMsg);
}
} // namespace unnamed
......@@ -40,6 +41,14 @@ void hard_exec(PGconn * conn, char const * query, char const * errMsg)
postgresql_session_backend::postgresql_session_backend(
connection_parameters const& parameters, bool single_row_mode)
: statementCount_(0)
{
single_row_mode_ = single_row_mode;
connect(parameters);
}
void postgresql_session_backend::connect(
connection_parameters const& parameters)
{
PGconn* conn = PQconnectdb(parameters.get_connect_string().c_str());
if (0 == conn || CONNECTION_OK != PQstatus(conn))
......@@ -60,13 +69,11 @@ postgresql_session_backend::postgresql_session_backend(
// case with the default value of 0. Use the maximal supported value, which
// was 2 until 9.x and is 3 since it.
int const version = PQserverVersion(conn);
hard_exec(conn,
hard_exec(*this, conn,
version >= 90000 ? "SET extra_float_digits = 3"
: "SET extra_float_digits = 2",
"Cannot set extra_float_digits parameter");
single_row_mode_ = single_row_mode;
conn_ = conn;
}
......@@ -77,17 +84,17 @@ postgresql_session_backend::~postgresql_session_backend()
void postgresql_session_backend::begin()
{
hard_exec(conn_, "BEGIN", "Cannot begin transaction.");
hard_exec(*this, conn_, "BEGIN", "Cannot begin transaction.");
}
void postgresql_session_backend::commit()
{
hard_exec(conn_, "COMMIT", "Cannot commit transaction.");
hard_exec(*this, conn_, "COMMIT", "Cannot commit transaction.");
}
void postgresql_session_backend::rollback()
{
hard_exec(conn_, "ROLLBACK", "Cannot rollback transaction.");
hard_exec(*this, conn_, "ROLLBACK", "Cannot rollback transaction.");
}
void postgresql_session_backend::deallocate_prepared_statement(
......@@ -95,7 +102,7 @@ void postgresql_session_backend::deallocate_prepared_statement(
{
const std::string & query = "DEALLOCATE " + statementName;
hard_exec(conn_, query.c_str(),
hard_exec(*this, conn_, query.c_str(),
"Cannot deallocate prepared statement.");
}
......
......@@ -59,10 +59,11 @@ void throw_soci_error(PGconn * conn, const char * msg)
postgresql_statement_backend::postgresql_statement_backend(
postgresql_session_backend &session, bool single_row_mode)
: session_(session), single_row_mode_(single_row_mode)
, rowsAffectedBulk_(-1LL), justDescribed_(false)
, hasIntoElements_(false), hasVectorIntoElements_(false)
, hasUseElements_(false), hasVectorUseElements_(false)
: session_(session), single_row_mode_(single_row_mode),
result_(session, NULL),
rowsAffectedBulk_(-1LL), justDescribed_(false),
hasIntoElements_(false), hasVectorIntoElements_(false),
hasUseElements_(false), hasVectorUseElements_(false)
{
}
......@@ -235,7 +236,7 @@ void postgresql_statement_backend::prepare(std::string const & query,
{
// default multi-row query execution
postgresql_result result(
postgresql_result result(session_,
PQprepare(session_.conn_, statementName.c_str(),
query_.c_str(), static_cast<int>(names_.size()), NULL));
result.check_for_errors("Cannot prepare statement.");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment