683 lines
18 KiB
C++
683 lines
18 KiB
C++
/**
|
|
* pgsqldb.cpp
|
|
* This file is part of the YATE Project http://YATE.null.ro
|
|
*
|
|
* This is the PostgreSQL support from Yate.
|
|
*
|
|
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
|
* Copyright (C) 2004-2014 Null Team
|
|
*
|
|
* This software is distributed under multiple licenses;
|
|
* see the COPYING file in the main directory for licensing
|
|
* information for this specific distribution.
|
|
*
|
|
* This use of this software may be subject to additional restrictions.
|
|
* See the LEGAL file in the main directory for details.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
*/
|
|
|
|
#include <yatephone.h>
|
|
|
|
#include <stdio.h>
|
|
#include <libpq-fe.h>
|
|
|
|
using namespace TelEngine;
|
|
namespace { // anonymous
|
|
|
|
class PGConn; // A database connection
|
|
class PgAccount; // Database account holding the connection(s)
|
|
|
|
static ObjList s_accounts;
|
|
Mutex s_conmutex(false,"PgSQL::acc");
|
|
static unsigned int s_failedConns;
|
|
|
|
// A database connection
|
|
class PgConn : public String
|
|
{
|
|
friend class PgAccount;
|
|
public:
|
|
PgConn(PgAccount* account = 0);
|
|
~PgConn();
|
|
inline bool isBusy() const
|
|
{ return m_busy; }
|
|
inline void setBusy(bool busy)
|
|
{ m_busy = busy; }
|
|
// Test if the connection is still OK
|
|
inline bool testDb() const
|
|
{ return m_conn && (CONNECTION_OK == PQstatus(m_conn)); }
|
|
bool initDb();
|
|
void dropDb();
|
|
// Perform the query, fill the message with data
|
|
// Return number of rows, -1 for non-retryable errors and -2 to retry
|
|
int queryDb(const char* query, Message* dest);
|
|
virtual void destruct();
|
|
private:
|
|
// Init DB connection
|
|
bool initDbInternal(int retry);
|
|
// Perform the query, fill the message with data
|
|
// Return number of rows, -1 for non-retryable errors and -2 to retry
|
|
int queryDbInternal(const char* query, Message* dest);
|
|
|
|
PgAccount* m_account;
|
|
bool m_busy;
|
|
PGconn* m_conn;
|
|
};
|
|
|
|
// Database account holding the connection(s)
|
|
class PgAccount : public RefObject, public Mutex
|
|
{
|
|
friend class PgConn;
|
|
public:
|
|
PgAccount(const NamedList& sect);
|
|
// Try to initialize DB connections. Return true if at least one of them is active
|
|
bool initDb();
|
|
// Make a query
|
|
int queryDb(const char* query, Message* dest);
|
|
bool hasConn();
|
|
virtual const String& toString() const
|
|
{ return m_name; }
|
|
virtual void destroyed();
|
|
|
|
inline unsigned int total()
|
|
{ return m_totalQueries; }
|
|
inline unsigned int failed()
|
|
{ return m_failedQueries; }
|
|
inline unsigned int errorred()
|
|
{ return m_errorQueries; }
|
|
inline unsigned int queryTime()
|
|
{ return (unsigned int) m_queryTime; }
|
|
|
|
protected:
|
|
inline void incErrorQueriesSafe() {
|
|
Lock mylock(m_statsMutex);
|
|
m_errorQueries++;
|
|
}
|
|
|
|
private:
|
|
void dropDb();
|
|
|
|
String m_name;
|
|
String m_connection;
|
|
String m_encoding;
|
|
int m_retry;
|
|
u_int64_t m_timeout;
|
|
PgConn* m_connPool;
|
|
unsigned int m_connPoolSize;
|
|
// stat counters
|
|
Mutex* m_statsMutex;
|
|
unsigned int m_totalQueries;
|
|
unsigned int m_failedQueries;
|
|
unsigned int m_errorQueries;
|
|
u_int64_t m_queryTime;
|
|
};
|
|
|
|
class PgModule : public Module
|
|
{
|
|
public:
|
|
PgModule();
|
|
~PgModule();
|
|
protected:
|
|
virtual void initialize();
|
|
virtual void statusModule(String& str);
|
|
virtual void statusParams(String& str);
|
|
virtual void statusDetail(String& str);
|
|
virtual void genUpdate(Message& msg);
|
|
private:
|
|
bool m_init;
|
|
};
|
|
|
|
static PgModule module;
|
|
|
|
|
|
class PgHandler : public MessageHandler
|
|
{
|
|
public:
|
|
PgHandler(unsigned int prio = 100)
|
|
: MessageHandler("database",prio,module.name())
|
|
{ }
|
|
virtual bool received(Message& msg);
|
|
};
|
|
|
|
|
|
//
|
|
// PgConn
|
|
//
|
|
PgConn::PgConn(PgAccount* account)
|
|
: m_account(account), m_busy(false),
|
|
m_conn(0)
|
|
{
|
|
}
|
|
|
|
PgConn::~PgConn()
|
|
{
|
|
dropDb();
|
|
}
|
|
|
|
// Initialize the database connection and handler data
|
|
bool PgConn::initDb()
|
|
{
|
|
if (testDb())
|
|
return true;
|
|
int retry = m_account->m_retry;
|
|
for (int i = 0; i < retry; i++) {
|
|
if (initDbInternal(i + 1))
|
|
return true;
|
|
Thread::yield();
|
|
if (testDb())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Drop the connection
|
|
void PgConn::dropDb()
|
|
{
|
|
if (!m_conn)
|
|
return;
|
|
PGconn* tmp = m_conn;
|
|
m_conn = 0;
|
|
XDebug(&module,DebugAll,"Connection '%s' dropped [%p]",c_str(),m_account);
|
|
PQfinish(tmp);
|
|
}
|
|
|
|
// Perform the query, fill the message with data
|
|
// Return number of rows, -1 for non-retryable errors and -2 to retry
|
|
int PgConn::queryDb(const char* query, Message* dest)
|
|
{
|
|
int retry = m_account->m_retry;
|
|
for (int i = 0; i < retry; i++) {
|
|
XDebug(&module,DebugAll,"Connection '%s' performing query (retry=%d): %s [%p]",
|
|
c_str(),i + 1,query,m_account);
|
|
int res = queryDbInternal(query,dest);
|
|
if (res > -2)
|
|
return res;
|
|
}
|
|
return -2;
|
|
}
|
|
|
|
void PgConn::destruct()
|
|
{
|
|
dropDb();
|
|
String::destruct();
|
|
}
|
|
|
|
// Init DB connection
|
|
bool PgConn::initDbInternal(int retry)
|
|
{
|
|
dropDb();
|
|
// Allow specifying the raw connection string
|
|
Debug(&module,DebugAll,"'%s' intializing connection \"%s\" retry %d [%p]",
|
|
c_str(),m_account->m_connection.c_str(),retry,m_account);
|
|
u_int64_t timeout = Time::now() + m_account->m_timeout;
|
|
m_conn = PQconnectStart(m_account->m_connection.c_str());
|
|
if (!m_conn) {
|
|
Debug(&module,DebugCrit,"Could not start connection for '%s' [%p]",c_str(),m_account);
|
|
return false;
|
|
}
|
|
PQsetnonblocking(m_conn,1);
|
|
Thread::msleep(1);
|
|
PostgresPollingStatusType polling = PGRES_POLLING_OK;
|
|
struct timeval tm;
|
|
Time::toTimeval(&tm,Thread::idleUsec());
|
|
while (Time::now() < timeout) {
|
|
if (PGRES_POLLING_WRITING == polling || PGRES_POLLING_READING == polling) {
|
|
// The Postgres library should have done all this internally...
|
|
Socket sock(PQsocket(m_conn));
|
|
bool ok = sock.canSelect();
|
|
bool fatal = false;
|
|
if (ok) {
|
|
ok = false;
|
|
bool* readOk = (PGRES_POLLING_READING == polling) ? &ok : 0;
|
|
bool* writeOk = (PGRES_POLLING_WRITING == polling) ? &ok : 0;
|
|
if (!sock.select(readOk,writeOk,0,&tm)) {
|
|
if (sock.canRetry()) {
|
|
Thread::idle();
|
|
ok = false;
|
|
}
|
|
else {
|
|
fatal = true;
|
|
Debug(&module,DebugWarn,
|
|
"Connection for '%s' failed: socket select failed [%p]",
|
|
c_str(),m_account);
|
|
}
|
|
}
|
|
|
|
}
|
|
else {
|
|
fatal = true;
|
|
Debug(&module,DebugWarn,
|
|
"Connection for '%s' failed: socket not selectable [%p]",
|
|
c_str(),m_account);
|
|
}
|
|
sock.detach();
|
|
if (fatal) {
|
|
dropDb();
|
|
return false;
|
|
}
|
|
if (!ok)
|
|
continue;
|
|
}
|
|
polling = PQconnectPoll(m_conn);
|
|
switch (PQstatus(m_conn)) {
|
|
case CONNECTION_BAD:
|
|
Debug(&module,DebugWarn,"Connection for '%s' failed: %s [%p]",
|
|
c_str(),PQerrorMessage(m_conn),m_account);
|
|
dropDb();
|
|
return false;
|
|
case CONNECTION_OK:
|
|
Debug(&module,DebugAll,"Connection for '%s' succeeded [%p]",c_str(),m_account);
|
|
if (m_account->m_encoding && PQsetClientEncoding(m_conn,m_account->m_encoding))
|
|
Debug(&module,DebugWarn,
|
|
"Failed to set encoding '%s' on connection '%s' [%p]",
|
|
m_account->m_encoding.c_str(),c_str(),m_account);
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
Thread::idle();
|
|
if (Thread::check(false))
|
|
return false;
|
|
}
|
|
Debug(&module,DebugWarn,"Connection for '%s' timed out [%p]",c_str(),m_account);
|
|
dropDb();
|
|
return false;
|
|
}
|
|
|
|
// Perform the query, fill the message with data
|
|
// Return number of rows, -1 for non-retryable errors and -2 to retry
|
|
int PgConn::queryDbInternal(const char* query, Message* dest)
|
|
{
|
|
if (!initDb())
|
|
// no retry - initDb already tried and failed...
|
|
return -1;
|
|
u_int64_t timeout = Time::now() + m_account->m_timeout;
|
|
if (!PQsendQuery(m_conn,query)) {
|
|
// a connection failure cannot be detected at this point so any
|
|
// error must be caused by the query itself - bad syntax or so
|
|
Debug(&module,DebugWarn,"Query '%s' for '%s' failed: %s [%p]",
|
|
query,c_str(),PQerrorMessage(m_conn),m_account);
|
|
if (dest)
|
|
dest->setParam("error",PQerrorMessage(m_conn));
|
|
// non-retryable, query should be fixed
|
|
return -1;
|
|
}
|
|
|
|
if (PQflush(m_conn)) {
|
|
Debug(&module,DebugWarn,"Flush for '%s' failed: %s [%p]",
|
|
c_str(),PQerrorMessage(m_conn),m_account);
|
|
dropDb();
|
|
if (dest)
|
|
dest->setParam("error",PQerrorMessage(m_conn));
|
|
return -2;
|
|
}
|
|
|
|
int totalRows = 0;
|
|
int affectedRows = 0;
|
|
while (Time::now() < timeout) {
|
|
PQconsumeInput(m_conn);
|
|
if (PQisBusy(m_conn)) {
|
|
Thread::yield();
|
|
continue;
|
|
}
|
|
PGresult* res = PQgetResult(m_conn);
|
|
if (!res) {
|
|
// last result already received and processed - exit successfully
|
|
Debug(&module,DebugAll,"Query for '%s' returned %d rows, %d affected [%p]",
|
|
c_str(),totalRows,affectedRows,m_account);
|
|
if (dest) {
|
|
dest->setParam("rows",String(totalRows));
|
|
dest->setParam("affected",String(affectedRows));
|
|
}
|
|
return totalRows;
|
|
}
|
|
ExecStatusType stat = PQresultStatus(res);
|
|
switch (stat) {
|
|
case PGRES_TUPLES_OK:
|
|
// we got some data - but maybe zero rows or binary...
|
|
if (dest) {
|
|
affectedRows += String(PQcmdTuples(res)).toInteger();
|
|
int columns = PQnfields(res);
|
|
int rows = PQntuples(res);
|
|
if (rows > 0) {
|
|
totalRows += rows;
|
|
dest->setParam("columns",String(columns));
|
|
if (dest->getBoolValue("results",true) && !PQbinaryTuples(res)) {
|
|
Array *a = new Array(columns,rows+1);
|
|
for (int k = 0; k < columns; k++) {
|
|
ObjList* column = a->getColumn(k);
|
|
if (column)
|
|
column->set(new String(PQfname(res,k)));
|
|
else {
|
|
Debug(&module,DebugCrit,
|
|
"Query '%s' for '%s': No array column for %d [%p]",
|
|
query,c_str(),k,m_account);
|
|
continue;
|
|
}
|
|
for (int j = 0; j < rows; j++) {
|
|
column = column->next();
|
|
if (!column) {
|
|
// Stop now: we won't get the next row
|
|
Debug(&module,DebugCrit,
|
|
"Query '%s' for '%s': No array row %d in column %d [%p]",
|
|
query,c_str(),j + 1,k,m_account);
|
|
break;
|
|
}
|
|
// skip over NULL values
|
|
if (PQgetisnull(res,j,k))
|
|
continue;
|
|
GenObject* v = 0;
|
|
if (PQfformat(res,k))
|
|
v = new DataBlock(PQgetvalue(res,j,k),PQgetlength(res,j,k));
|
|
else
|
|
v = new String(PQgetvalue(res,j,k));
|
|
column->set(v);
|
|
}
|
|
}
|
|
dest->userData(a);
|
|
a->deref();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case PGRES_COMMAND_OK:
|
|
if (dest)
|
|
affectedRows += String(PQcmdTuples(res)).toInteger();
|
|
// no data returned
|
|
break;
|
|
case PGRES_COPY_IN:
|
|
case PGRES_COPY_OUT:
|
|
// data transfers - ignore them
|
|
break;
|
|
default:
|
|
Debug(&module,DebugWarn,"Query '%s' for '%s' error: %s [%p]",
|
|
query,c_str(),PQresultErrorMessage(res),m_account);
|
|
if (dest)
|
|
dest->setParam("error",PQresultErrorMessage(res));
|
|
m_account->incErrorQueriesSafe();
|
|
module.changed();
|
|
}
|
|
PQclear(res);
|
|
}
|
|
Debug(&module,DebugWarn,"Query timed out for '%s' [%p]",c_str(),m_account);
|
|
if (dest)
|
|
dest->setParam("error","query timeout");
|
|
dropDb();
|
|
return -2;
|
|
}
|
|
|
|
|
|
//
|
|
// PgAccount
|
|
//
|
|
PgAccount::PgAccount(const NamedList& sect)
|
|
: Mutex(true,"PgAccount"),
|
|
m_name(sect),
|
|
m_connPool(0), m_connPoolSize(0),
|
|
m_statsMutex(&s_conmutex),
|
|
m_totalQueries(0), m_failedQueries(0),
|
|
m_errorQueries(0), m_queryTime(0)
|
|
{
|
|
m_connection = sect.getValue("connection");
|
|
if (m_connection.null()) {
|
|
// build connection string from pieces
|
|
String tmp = sect.getValue("host","localhost");
|
|
m_connection << "host='" << tmp << "'";
|
|
tmp = sect.getValue("port");
|
|
if (tmp)
|
|
m_connection << " port='" << tmp << "'";
|
|
tmp = sect.getValue("database","yate");
|
|
m_connection << " dbname='" << tmp << "'";
|
|
tmp = sect.getValue("user","postgres");
|
|
m_connection << " user='" << tmp << "'";
|
|
tmp = sect.getValue("password");
|
|
if (tmp)
|
|
m_connection << " password='" << tmp << "'";
|
|
}
|
|
m_timeout = (u_int64_t)1000 * sect.getIntValue("timeout",10000);
|
|
if (m_timeout < 500000)
|
|
m_timeout = 500000;
|
|
m_retry = sect.getIntValue("retry",5);
|
|
m_encoding = sect.getValue("encoding");
|
|
m_connPoolSize = sect.getIntValue("poolsize",1,1);
|
|
m_connPool = new PgConn[m_connPoolSize];
|
|
for (unsigned int i = 0; i < m_connPoolSize; i++) {
|
|
m_connPool[i].m_account = this;
|
|
m_connPool[i].assign(m_name + "." + String(i + 1));
|
|
}
|
|
Debug(&module,DebugInfo,"Database account '%s' created poolsize=%u [%p]",
|
|
m_name.c_str(),m_connPoolSize,this);
|
|
}
|
|
|
|
// Init the connections the connection
|
|
bool PgAccount::initDb()
|
|
{
|
|
bool ok = false;
|
|
for (unsigned int i = 0; i < m_connPoolSize; i++)
|
|
ok = m_connPool[i].initDb() || ok;
|
|
return ok;
|
|
}
|
|
|
|
void PgAccount::destroyed()
|
|
{
|
|
s_conmutex.lock();
|
|
s_accounts.remove(this,false);
|
|
s_conmutex.unlock();
|
|
dropDb();
|
|
if (m_connPool)
|
|
delete[] m_connPool;
|
|
m_connPoolSize = 0;
|
|
Debug(&module,DebugInfo,"Database account '%s' destroyed [%p]",m_name.c_str(),this);
|
|
}
|
|
|
|
// drop the connection
|
|
void PgAccount::dropDb()
|
|
{
|
|
for (unsigned int i = 0; i < m_connPoolSize; i++)
|
|
m_connPool[i].dropDb();
|
|
}
|
|
|
|
static bool failure(Message* m)
|
|
{
|
|
if (m)
|
|
m->setParam("error","failure");
|
|
return false;
|
|
}
|
|
|
|
int PgAccount::queryDb(const char* query, Message* dest)
|
|
{
|
|
if (TelEngine::null(query))
|
|
return -1;
|
|
Debug(&module,DebugAll,"Performing query \"%s\" for '%s'",
|
|
query,m_name.c_str());
|
|
// Use a while() to break to the end to update statistics
|
|
int res = -1;
|
|
u_int64_t start = Time::now();
|
|
while (true) {
|
|
Lock mylock(this,(long)m_timeout);
|
|
if (!mylock.locked()) {
|
|
Debug(&module,DebugWarn,"Failed to lock '%s' for " FMT64U " usec",
|
|
m_name.c_str(),m_timeout);
|
|
break;
|
|
}
|
|
// Find a non busy connection
|
|
PgConn* conn = 0;
|
|
PgConn* notConnected = 0;
|
|
for (unsigned int i = 0; i < m_connPoolSize; i++) {
|
|
if (m_connPool[i].isBusy())
|
|
continue;
|
|
if (m_connPool[i].testDb()) {
|
|
conn = &(m_connPool[i]);
|
|
break;
|
|
}
|
|
if (!notConnected)
|
|
notConnected = &(m_connPool[i]);
|
|
}
|
|
if (!conn)
|
|
conn = notConnected;
|
|
if (!conn) {
|
|
// Wait for a connection to become non-busy
|
|
// Round up the number of intervals to wait
|
|
unsigned int n = (unsigned int)((m_timeout + 999999) / Thread::idleUsec());
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
for (unsigned int j = 0; j < m_connPoolSize; j++) {
|
|
if (!m_connPool[j].isBusy() && m_connPool[j].testDb()) {
|
|
conn = &(m_connPool[j]);
|
|
break;
|
|
}
|
|
}
|
|
if (conn || Thread::check(false))
|
|
break;
|
|
Thread::idle();
|
|
}
|
|
}
|
|
if (conn)
|
|
conn->setBusy(true);
|
|
else
|
|
Debug(&module,DebugWarn,"Account '%s' failed to pick a connection [%p]",m_name.c_str(),this);
|
|
mylock.drop();
|
|
if (conn) {
|
|
res = conn->queryDb(query,dest);
|
|
conn->setBusy(false);
|
|
}
|
|
break;
|
|
}
|
|
Lock stats(m_statsMutex);
|
|
m_totalQueries++;
|
|
if (res > -2) {
|
|
if (res < 0)
|
|
m_failedQueries++;
|
|
u_int64_t finish = Time::now() - start;
|
|
m_queryTime += finish;
|
|
}
|
|
stats.drop();
|
|
module.changed();
|
|
if (res < 0)
|
|
failure(dest);
|
|
return res;
|
|
}
|
|
|
|
bool PgAccount::hasConn()
|
|
{
|
|
for (unsigned int i = 0; i < m_connPoolSize; i++)
|
|
if (m_connPool[i].testDb())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static PgAccount* findDb(const String& account)
|
|
{
|
|
if (account.null())
|
|
return 0;
|
|
return static_cast<PgAccount*>(s_accounts[account]);
|
|
}
|
|
|
|
bool PgHandler::received(Message& msg)
|
|
{
|
|
const String* str = msg.getParam("account");
|
|
if (TelEngine::null(str))
|
|
return false;
|
|
s_conmutex.lock();
|
|
RefPointer<PgAccount> db = findDb(*str);
|
|
s_conmutex.unlock();
|
|
if (!db)
|
|
return false;
|
|
str = msg.getParam("query");
|
|
if (!TelEngine::null(str))
|
|
db->queryDb(*str,&msg);
|
|
db = 0;
|
|
msg.setParam("dbtype","pgsqldb");
|
|
return true;
|
|
}
|
|
|
|
PgModule::PgModule()
|
|
: Module ("pgsqldb","database",true),m_init(false)
|
|
{
|
|
Output("Loaded module PostgreSQL");
|
|
}
|
|
|
|
PgModule::~PgModule()
|
|
{
|
|
Output("Unloading module PostgreSQL");
|
|
s_accounts.clear();
|
|
}
|
|
|
|
void PgModule::statusModule(String& str)
|
|
{
|
|
Module::statusModule(str);
|
|
str.append("format=Total|Failed|Errors|AvgExecTime",",");
|
|
}
|
|
|
|
void PgModule::statusParams(String& str)
|
|
{
|
|
s_conmutex.lock();
|
|
str.append("conns=",",") << s_accounts.count();
|
|
str.append("failed=",",") << s_failedConns;
|
|
s_conmutex.unlock();
|
|
}
|
|
|
|
void PgModule::statusDetail(String& str)
|
|
{
|
|
s_conmutex.lock();
|
|
for (ObjList* o = s_accounts.skipNull(); o; o = o->skipNext()) {
|
|
PgAccount* acc = static_cast<PgAccount*>(o->get());
|
|
str.append(acc->toString().c_str(),",") << "=" << acc->total() << "|" << acc->failed()
|
|
<< "|" << acc->errorred() << "|";
|
|
if (acc->total() - acc->failed() > 0)
|
|
str << (acc->queryTime() / (acc->total() - acc->failed()) / 1000); //miliseconds
|
|
else
|
|
str << "0";
|
|
}
|
|
s_conmutex.unlock();
|
|
}
|
|
|
|
void PgModule::initialize()
|
|
{
|
|
Module::initialize();
|
|
if (m_init)
|
|
return;
|
|
m_init = true;
|
|
Output("Initializing module PostgreSQL");
|
|
Configuration cfg(Engine::configFile("pgsqldb"));
|
|
Engine::install(new PgHandler(cfg.getIntValue("general","priority",100)));
|
|
unsigned int i;
|
|
for (i = 0; i < cfg.sections(); i++) {
|
|
NamedList* sec = cfg.getSection(i);
|
|
if (!sec || (*sec == "general"))
|
|
continue;
|
|
PgAccount* acc = new PgAccount(*sec);
|
|
if (sec->getBoolValue("autostart",true) && !acc->initDb())
|
|
TelEngine::destruct(acc);
|
|
s_conmutex.lock();
|
|
if (acc)
|
|
s_accounts.insert(acc);
|
|
else
|
|
s_failedConns++;
|
|
s_conmutex.unlock();
|
|
}
|
|
}
|
|
|
|
void PgModule::genUpdate(Message& msg)
|
|
{
|
|
unsigned int index = 0;
|
|
s_conmutex.lock();
|
|
for (ObjList* o = s_accounts.skipNull(); o; o = o->skipNext()) {
|
|
PgAccount* acc = static_cast<PgAccount*>(o->get());
|
|
msg.setParam(String("database.") << index,acc->toString());
|
|
msg.setParam(String("total.") << index,String(acc->total()));
|
|
msg.setParam(String("failed.") << index,String(acc->failed()));
|
|
msg.setParam(String("errorred.") << index,String(acc->errorred()));
|
|
msg.setParam(String("hasconn.") << index,String::boolText(acc->hasConn()));
|
|
msg.setParam(String("querytime.") << index,String(acc->queryTime()));
|
|
index++;
|
|
}
|
|
s_conmutex.unlock();
|
|
msg.setParam("count",String(index));
|
|
}
|
|
|
|
}; // anonymous namespace
|
|
|
|
/* vi: set ts=8 sw=4 sts=4 noet: */
|