From ba13dedd67fa4e4174b5559c14d9cf910b42cde7 Mon Sep 17 00:00:00 2001 From: paulc Date: Tue, 16 Sep 2014 13:04:05 +0000 Subject: [PATCH] Added SQLite database support. git-svn-id: http://yate.null.ro/svn/yate/trunk@5913 acf43c95-373e-0410-b603-e72c3f656dc1 --- conf.d/sqlitedb.conf.sample | 87 +++++ configure.in | 30 ++ modules/Makefile.in | 10 + modules/server/sqlitedb.cpp | 648 ++++++++++++++++++++++++++++++++++++ packing/rpm/yate.spec.in | 18 + 5 files changed, 793 insertions(+) create mode 100644 conf.d/sqlitedb.conf.sample create mode 100644 modules/server/sqlitedb.cpp diff --git a/conf.d/sqlitedb.conf.sample b/conf.d/sqlitedb.conf.sample new file mode 100644 index 00000000..f672d356 --- /dev/null +++ b/conf.d/sqlitedb.conf.sample @@ -0,0 +1,87 @@ +[general] +; This section is special - holds settings common to all connections + +; priority: int: Handler priority for the "database" message +;priority=100 + +; shared_cache: bool: Use the SQLite shared cache by default +; Cache sharing can be enabled or disabled per database +;shared_cache=no + + +; Each other section in this file describes a database connection + +;[default] +; The section name is used as the database connection name + +; autostart: bool: Automatically initiate the connection on startup +;autostart=yes + +; timeout: int: Query timeout in milliseconds +; Minimum value for timeout is 100 +;timeout=2000 + +; retry: int: How many times to retry operations in case of contention +; Valid values are 0..100 +;retry=5 + +; database: string: SQLite database filename or descriptor +; It can be a file name, a file: URI or a special value +; An empty string creates a non-sharable temporary file database +; The special value :memory: creates an in-memory non-sharable database +; Engine runtime parameters like ${configpath} will be substituted +;database=:memory: + +; initialize: string: Semicolon separated SQLite queries to run after opening the database +; This can be used to apply various behavior changing PRAGMA or to populate a memory or +; temporary database (which is initially empty) +; If the first character is a @ then this setting indicates the name of a file holding the +; SQLite queries. Engine runtime parameters will be substituted in the file name. +;initialize= + +; poolsize: int: Number of connections to establish for this account +; Pooling can be enabled only for shared cache databases +; Minimum number of connections is 1 +;poolsize=1 +[general] +; This section is special - holds settings common to all connections + +; priority: int: Handler priority for the "database" message +;priority=100 + +; shared_cache: bool: Use the SQLite shared cache by default +; Cache sharing can be enabled or disabled per database +;shared_cache=no + + +; Each other section in this file describes a database connection + +;[default] +; The section name is used as the database connection name + +; autostart: bool: Automatically initiate the connection on startup +;autostart=yes + +; timeout: int: Query timeout in milliseconds +; Minimum value for timeout is 100 +;timeout=2000 + +; retry: int: How many times to retry operations in case of contention +; Valid values are 0..100 +;retry=5 + +; database: string: SQLite database filename or descriptor +; It can be a file name, a file: URI or a special value +; An empty string creates a non-sharable temporary file database +; The special value :memory: creates an in-memory non-sharable database +;database=:memory: + +; initialize: string: Semicolon separated SQLite queries to run after opening the database +; This can be used to apply various behavior changing PRAGMA or to populate a memory or +; temporary database (which is initially empty) +;initialize= + +; poolsize: int: Number of connections to establish for this account +; Pooling can be enabled only for shared cache databases +; Minimum number of connections is 1 +;poolsize=1 diff --git a/configure.in b/configure.in index a086ecbe..a44578d1 100644 --- a/configure.in +++ b/configure.in @@ -689,6 +689,36 @@ AC_SUBST(MYSQL_INC) AC_SUBST(MYSQL_LIB) AC_SUBST(MYSQL_VER) +HAVE_SQLITE=no +SQLITE_INC="" +SQLITE_LIB="" +AC_ARG_WITH(sqlite,AC_HELP_STRING([--with-sqlite=DIR],[use SQLite library from DIR]),[ac_cv_use_sqlite=$withval],[ac_cv_use_sqlite=yes]) +if [[ "x$ac_cv_use_sqlite" != "xno" ]]; then +if [[ "x$ac_cv_use_sqlite" = "xyes" ]]; then +AC_MSG_CHECKING([for SQLite using pkg-config]) +SQLITE_INC=`(pkg-config --cflags sqlite3) 2>/dev/null` +SQLITE_LIB=`(pkg-config --libs sqlite3) 2>/dev/null` +if [[ "x$SQLITE_INC$SQLITE_LIB" = "x" ]]; then +SQLITE_INC="" +SQLITE_LIB="" +else +HAVE_SQLITE=yes +fi +else +AC_MSG_CHECKING([for SQLite in $ac_cv_use_sqlite]) +if [[ -f "$ac_cv_use_sqlite/sqlite3.h" ]]; then + SQLITE_INC="-I$ac_cv_use_sqlite" + SQLITE_LIB="-lsqlite3" + HAVE_SQLITE=yes +fi +fi +AC_MSG_RESULT([$HAVE_SQLITE]) +fi +AC_SUBST(HAVE_SQLITE) +AC_SUBST(SQLITE_INC) +AC_SUBST(SQLITE_LIB) + + HAVE_ZAP=no ZAP_FLAGS="" AC_ARG_ENABLE(dahdi,AC_HELP_STRING([--enable-dahdi],[Enable Dahdi driver (default: yes)]),want_dahdi=$enableval,want_dahdi=yes) diff --git a/modules/Makefile.in b/modules/Makefile.in index 5be28d20..c408475e 100644 --- a/modules/Makefile.in +++ b/modules/Makefile.in @@ -25,6 +25,9 @@ PGSQL_LIB := -lpq HAVE_MYSQL := @HAVE_MYSQL@ MYSQL_INC := @MYSQL_INC@ MYSQL_LIB := @MYSQL_LIB@ +HAVE_SQLITE := @HAVE_SQLITE@ +SQLITE_INC := @SQLITE_INC@ +SQLITE_LIB := @SQLITE_LIB@ HAVE_SPANDSP := @HAVE_SPANDSP@ SPANDSP_INC := @SPANDSP_INC@ SPANDSP_LIB := @SPANDSP_LIB@ @@ -97,6 +100,10 @@ ifneq ($(HAVE_MYSQL),no) PROGS := $(PROGS) server/mysqldb.yate endif +ifneq ($(HAVE_SQLITE),no) +PROGS := $(PROGS) server/sqlitedb.yate +endif + ifneq (@HAVE_RESOLV@,no) PROGS := $(PROGS) enumroute.yate endif @@ -325,6 +332,9 @@ server/pgsqldb.yate: EXTERNLIBS = $(PGSQL_LIB) server/mysqldb.yate: EXTERNFLAGS = $(MYSQL_INC) server/mysqldb.yate: EXTERNLIBS = $(MYSQL_LIB) +server/sqlitedb.yate: EXTERNFLAGS = $(SQLITE_INC) +server/sqlitedb.yate: EXTERNLIBS = $(SQLITE_LIB) + client/alsachan.yate: EXTERNLIBS = -lasound client/coreaudio.yate: EXTERNLIBS = -framework CoreServices -framework CoreAudio -framework AudioUnit -framework AudioToolbox diff --git a/modules/server/sqlitedb.cpp b/modules/server/sqlitedb.cpp new file mode 100644 index 00000000..1ae1337e --- /dev/null +++ b/modules/server/sqlitedb.cpp @@ -0,0 +1,648 @@ +/** + * sqlitedb.cpp + * This file is part of the YATE Project http://YATE.null.ro + * + * This is the SQLite support from Yate. + * + * Yet Another Telephony Engine - a fully featured software PBX and IVR + * Copyright (C) 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 + +#include +#include + +using namespace TelEngine; +namespace { // anonymous + +class SqlConn; + +static ObjList s_accounts; +Mutex s_conmutex(false,"SQLite::acc"); +static unsigned int s_failedConns; +static bool s_sharedCache = false; + +// Database account holding the connection(s) +class SqlAccount : public RefObject, public Mutex +{ + friend class SqlConn; +public: + SqlAccount(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_database; + String m_initialize; + int m_retry; + u_int64_t m_timeout; + SqlConn* 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; +}; + +// A database connection +class SqlConn : public String +{ + friend class SqlAccount; +public: + SqlConn(SqlAccount* account = 0); + ~SqlConn(); + inline bool isBusy() const + { return m_busy; } + inline void setBusy(bool busy) + { m_busy = busy; } + inline int retries() const + { return m_account ? m_account->m_retry : 5; } + // Test if the connection is still OK + inline bool testDb() const + { return m_conn != 0; } + bool initDb(); + void dropDb(); + // Perform the query, fill the message with data, retry in case of errors + // Return number of rows, -1 for non-retryable errors and -2 for busy / timeout + int queryDb(const char* query, Message* dest); + virtual void destruct(); +private: + SqlAccount* m_account; + bool m_busy; + sqlite3* m_conn; +}; + +class SqlModule : public Module +{ +public: + SqlModule(); + ~SqlModule(); +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 SqlModule module; + + +class SqlHandler : public MessageHandler +{ +public: + SqlHandler(unsigned int prio = 100) + : MessageHandler("database",prio,module.name()) + { } + virtual bool received(Message& msg); +}; + + +// +// SqlConn +// +SqlConn::SqlConn(SqlAccount* account) + : m_account(account), m_busy(false), + m_conn(0) +{ +} + +SqlConn::~SqlConn() +{ + dropDb(); +} + +// Initialize the database connection and handler data +bool SqlConn::initDb() +{ + if (testDb()) + return true; + dropDb(); + Debug(&module,DebugAll,"'%s' opening database \"%s\" [%p]", + c_str(),m_account->m_database.safe(),m_account); + if (sqlite3_open(m_account->m_database.safe(),&m_conn) != SQLITE_OK) { + Debug(&module,DebugWarn,"Failed to open database '%s': %s", + c_str(),sqlite3_errmsg(m_conn)); + dropDb(); + return false; + } + return true; +} + +// Drop the connection +void SqlConn::dropDb() +{ + if (!m_conn) + return; + sqlite3* tmp = m_conn; + m_conn = 0; + XDebug(&module,DebugAll,"Database '%s' dropped [%p]",c_str(),m_account); + if (sqlite3_close(tmp) != SQLITE_OK) + Debug(&module,DebugWarn,"Failed to close database '%s': %s", + c_str(),sqlite3_errmsg(tmp)); +} + +// Perform the query, fill the message with data, retry in case of errors +// Return number of rows, -1 for non-retryable errors and -2 for busy / timeout +int SqlConn::queryDb(const char* query, Message* dest) +{ + if (!initDb()) + // no retry - initDb already tried and failed... + return -1; + bool results = dest && dest->getBoolValue("results",true); + int changed = sqlite3_total_changes(m_conn); + int rows = 0; + int cols = -1; + + while (query) { + while (';' == *query || ' ' == *query || '\t' == *query || '\r' == *query || '\n' == *query) + query++; + if (!*query) + break; + int retry = retries(); + sqlite3_stmt* stmt; + const char* tail; + int i; + // Prepare statement, leave whatever unparsed in tail + for (i = 0; i >= 0; i++) { + if (i) + Thread::idle(); + stmt = 0; + tail = 0; + switch (sqlite3_prepare_v2(m_conn,query,-1,&stmt,&tail)) { + case SQLITE_OK: + i = -2; + break; + case SQLITE_BUSY: + case SQLITE_LOCKED: + sqlite3_finalize(stmt); + if (i >= retry) + return -2; + continue; + default: + { + const char* errStr = sqlite3_errmsg(m_conn); + Debug(&module,DebugWarn,"Query '%s' for '%s' prepare error: %s [%p]", + query,c_str(),errStr,m_account); + if (dest) + dest->setParam("error",errStr); + } + sqlite3_finalize(stmt); + if (results) + dest->userData(0); + return -1; + } + } + int lr = 0; + int lc = 0; + Array* a = 0; + // Execute statement, collect results if needed + for (i = 0; i >= 0; ) { + if (i) + Thread::idle(); + switch (sqlite3_step(stmt)) { + case SQLITE_DONE: + if (lr || !rows) { + rows = lr; + cols = lc; + if (results) { + dest->userData(a); + a = 0; + } + } + i = -2; + break; + case SQLITE_ROW: + if (!lr++) + lc = sqlite3_column_count(stmt); + if (!results) + continue; + if (!a) { + a = new Array(lc,2); + for (int j = 0; j < lc; j++) + a->set(new String(sqlite3_column_name(stmt,j)),j,0); + } + else + a->addRow(); + for (int j = 0; j < lc; j++) { + GenObject* v = 0; + switch (sqlite3_column_type(stmt,j)) { + case SQLITE_NULL: + break; + case SQLITE_BLOB: + { + // Must do this in two steps to guarantee call order + void* data = const_cast(sqlite3_column_blob(stmt,j)); + v = new DataBlock(data,sqlite3_column_bytes(stmt,j)); + } + break; + default: + v = new String(reinterpret_cast(sqlite3_column_text(stmt,j))); + } + a->set(v,j,lr); + } + continue; + case SQLITE_BUSY: + case SQLITE_LOCKED: + if (i++ >= retry) { + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + TelEngine::destruct(a); + if (results) + dest->userData(0); + return -2; + } + continue; + default: + { + const char* errStr = sqlite3_errmsg(m_conn); + Debug(&module,DebugWarn,"Query '%s' for '%s' execute error: %s [%p]", + query,c_str(),errStr,m_account); + if (dest) + dest->setParam("error",errStr); + } + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + TelEngine::destruct(a); + if (results) + dest->userData(0); + m_account->incErrorQueriesSafe(); + return -1; + } + } + // Clean up statement and advance to next one + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + TelEngine::destruct(a); + query = tail; + } + changed = sqlite3_total_changes(m_conn) - changed; + if (dest) { + dest->setParam("rows",String(rows)); + if (cols >= 0) + dest->setParam("columns",String(cols)); + dest->setParam("affected",String(changed)); + } + return rows; +} + +void SqlConn::destruct() +{ + dropDb(); + String::destruct(); +} + + +// +// SqlAccount +// +SqlAccount::SqlAccount(const NamedList& sect) + : Mutex(true,"SqlAccount"), + 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_database = sect.getValue("database",":memory:"); + Engine::runParams().replaceParams(m_database); + m_initialize = sect.getValue("initialize"); + if (m_initialize.startSkip("@",false)) { + Engine::runParams().replaceParams(m_initialize); + m_initialize.trimBlanks(); + File f; + if (f.openPath(m_initialize)) { + int64_t len = f.length(); + if (len > 0 && len <= 65536) { + DataBlock d(0,len + 1); + if (f.readData(d.data(),len) == len) + m_initialize = (const char*)d.data(); + else { + Debug(&module,DebugWarn,"Failed to read init file '%s'",m_initialize.c_str()); + m_initialize.clear(); + } + } + else { + Debug(&module,DebugWarn,"Empty or too long file '%s'",m_initialize.c_str()); + m_initialize.clear(); + } + } + else { + Debug(&module,DebugWarn,"Missing init file '%s'",m_initialize.c_str()); + m_initialize.clear(); + } + } + m_timeout = (u_int64_t)1000 * sect.getIntValue("timeout",2000); + if (m_timeout < 100000) + m_timeout = 100000; + m_retry = sect.getIntValue("retry",5,0,100,false); + // Can create just one connection to temporary or non shared cache in-memory databases + bool shared = s_sharedCache && !m_database.null(); + shared = shared && (m_database.find(":memory:") < 0) && (m_database.find("mode=memory") < 0); + shared = shared || (m_database.startsWith("file:") && (m_database.find("cache=shared") >= 0)); + m_connPoolSize = sect.getIntValue("poolsize",1,1); + if ((m_connPoolSize > 1) && !shared) { + Debug(&module,DebugConf,"Disabling pooling for non shared cache account '%s'", + m_name.c_str()); + m_connPoolSize = 1; + } + m_connPool = new SqlConn[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 for the account, run init query +bool SqlAccount::initDb() +{ + bool ok = false; + for (unsigned int i = 0; i < m_connPoolSize; i++) { + ok = m_connPool[i].initDb() || ok; + if (ok && m_initialize && (i == 0) && (m_connPool[i].queryDb(m_initialize,0) < 0)) + Debug(&module,DebugWarn,"Failed to run initializer for account '%s'",m_name.c_str()); + } + return ok; +} + +void SqlAccount::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 SqlAccount::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 SqlAccount::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 + SqlConn* conn = 0; + SqlConn* 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 SqlAccount::hasConn() +{ + for (unsigned int i = 0; i < m_connPoolSize; i++) + if (m_connPool[i].testDb()) + return true; + return false; +} + +static SqlAccount* findDb(const String& account) +{ + if (account.null()) + return 0; + return static_cast(s_accounts[account]); +} + +bool SqlHandler::received(Message& msg) +{ + const String* str = msg.getParam("account"); + if (TelEngine::null(str)) + return false; + s_conmutex.lock(); + RefPointer 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","sqlitedb"); + return true; +} + +SqlModule::SqlModule() + : Module ("sqlitedb","database",true),m_init(false) +{ + String tmp(sqlite3_libversion()); + if (tmp != SQLITE_VERSION) + Debug(this,DebugConf,"SQLite version mismatch: expecting %s but library is %s", + SQLITE_VERSION,tmp.c_str()); + Output("Loaded module SQLite based on %s",SQLITE_VERSION); +} + +SqlModule::~SqlModule() +{ + Output("Unloading module SQLite"); + s_accounts.clear(); + if (m_init) { + sqlite3_shutdown(); + m_init = false; + } +} + +void SqlModule::statusModule(String& str) +{ + Module::statusModule(str); + str.append("format=Total|Failed|Errors|AvgExecTime",","); +} + +void SqlModule::statusParams(String& str) +{ + s_conmutex.lock(); + str.append("conns=",",") << s_accounts.count(); + str.append("failed=",",") << s_failedConns; + s_conmutex.unlock(); +} + +void SqlModule::statusDetail(String& str) +{ + s_conmutex.lock(); + for (ObjList* o = s_accounts.skipNull(); o; o = o->skipNext()) { + SqlAccount* acc = static_cast(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 SqlModule::initialize() +{ + Module::initialize(); + if (m_init) + return; + Output("Initializing module SQLite"); + Configuration cfg(Engine::configFile("sqlitedb")); + s_sharedCache = cfg.getBoolValue("general","shared_cache",false); + int err = sqlite3_initialize(); + if (err != SQLITE_OK) { + Alarm(this,DebugWarn,"SQLite initialize failed, code %d",err); + return; + } + sqlite3_enable_shared_cache(s_sharedCache); + unsigned int i; + for (i = 0; i < cfg.sections(); i++) { + NamedList* sec = cfg.getSection(i); + if (!sec || (*sec == "general")) + continue; + SqlAccount* acc = new SqlAccount(*sec); + if (sec->getBoolValue("autostart",true) && !acc->initDb()) + TelEngine::destruct(acc); + s_conmutex.lock(); + if (acc) { + s_accounts.insert(acc); + m_init = true; + } + else + s_failedConns++; + s_conmutex.unlock(); + } + if (m_init) + Engine::install(new SqlHandler(cfg.getIntValue("general","priority",100))); + else + sqlite3_shutdown(); +} + +void SqlModule::genUpdate(Message& msg) +{ + unsigned int index = 0; + s_conmutex.lock(); + for (ObjList* o = s_accounts.skipNull(); o; o = o->skipNext()) { + SqlAccount* acc = static_cast(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: */ diff --git a/packing/rpm/yate.spec.in b/packing/rpm/yate.spec.in index 6d76e898..df736a77 100644 --- a/packing/rpm/yate.spec.in +++ b/packing/rpm/yate.spec.in @@ -428,6 +428,21 @@ that support database access will be able to use MySQL. %config(noreplace) %{_sysconfdir}/yate/mysqldb.conf +%package sqlite +Summary: SQLite database driver for Yate +Group: Applications/Communication +Requires: %{name} = %{version}-%{release} +Provides: %{name}-database + +%description sqlite +This package allows Yate to use SQLite database files. All modules +that support database access will be able to use SQLite. + +%files sqlite +%{_libdir}/yate/server/sqlitedb.yate +%config(noreplace) %{_sysconfdir}/yate/sqlitedb.conf + + %if "%{no_gui}" != "1" %package client-common @@ -622,6 +637,9 @@ rm -rf %{buildroot} %changelog +* Tue Apr 29 2014 Paul Chitescu +- Added SQLite module and subpackage + * Mon Apr 23 2012 Paul Chitescu - Added new support module gvoice