urbackup_backend/Database.cpp
2011-10-29 22:06:37 +02:00

422 lines
8.2 KiB
C++

/*************************************************************************
* UrBackup - Client/Server backup system
* Copyright (C) 2011 Martin Raiber
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/
#ifndef NO_SQLITE
#if defined(_WIN32) || defined(WIN32)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "vld.h"
#include "Interface/File.h"
#ifndef BDBPLUGIN
#include "Server.h"
#else
#ifdef LINUX
#include "bdbplugin/config.h"
#include DB_HEADER
#else
#include <db.h>
#endif
#include "Interface/Server.h"
#endif
#include "Query.h"
#include "sqlite/sqlite3.h"
extern "C"
{
#include "sqlite/shell.h"
}
#include "Database.h"
IMutex * CDatabase::lock_mutex=NULL;
int CDatabase::lock_count=0;
ICondition *CDatabase::unlock_cond=NULL;
struct UnlockNotification {
bool fired;
ICondition* cond;
IMutex *mutex;
};
static int callback(void *CPtr, int argc, char **argv, char **azColName)
{
CDatabase* db=(CDatabase*)CPtr;
db_nsingle_result result;
for(int i=0; i<argc; i++)
{
//printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
if( azColName[i] && argv[i])
result.insert(std::pair<std::string,std::string>(azColName[i], argv[i]) );
}
db->InsertResults(result);
return 0;
}
static void unlock_notify_cb(void **apArg, int nArg)
{
for(int i=0; i<nArg; i++)
{
UnlockNotification *p = (UnlockNotification *)apArg[i];
IScopedLock lock(p->mutex);
p->fired = true;
p->cond->notify_all();
}
}
CDatabase::~CDatabase()
{
destroyAllQueries();
for(std::map<int, IQuery*>::iterator iter=prepared_queries.begin();iter!=prepared_queries.end();++iter)
{
CQuery *q=(CQuery*)iter->second;
delete q;
}
prepared_queries.clear();
sqlite3_close(db);
}
bool CDatabase::Open(std::string pFile)
{
in_transaction=false;
if( sqlite3_open(pFile.c_str(), &db) )
{
Server->Log("Could not open db ["+pFile+"]");
sqlite3_close(db);
return false;
}
else
{
sqlite3_busy_timeout(db, 50);
Write("PRAGMA foreign_keys = ON");
Write("PRAGMA synchronous=NORMAL");
return true;
}
}
void CDatabase::initMutex(void)
{
lock_mutex=Server->createMutex();
unlock_cond=Server->createCondition();
}
void CDatabase::destroyMutex(void)
{
Server->destroy(lock_mutex);
Server->destroy(unlock_cond);
}
db_nresults CDatabase::ReadN(std::string pQuery)
{
//Server->Log("SQL Query(Read): "+pQuery);
results.clear();
char *zErrMsg = 0;
int rc=sqlite3_exec(db, pQuery.c_str(), callback, this, &zErrMsg);
if( rc!=SQLITE_OK )
{
Server->Log("SQL ERROR: "+(std::string)zErrMsg);
}
if( zErrMsg!=NULL )
sqlite3_free(zErrMsg);
return results;
}
db_results CDatabase::Read(std::string pQuery)
{
//Server->Log("SQL Query(Read): "+pQuery, LL_DEBUG);
IQuery *q=Prepare(pQuery, false);
db_results ret=q->Read();
delete ((CQuery*)q);
return ret;
}
bool CDatabase::Write(std::string pQuery)
{
//Server->Log("SQL Query(Write): "+pQuery, LL_DEBUG);
IQuery *q=Prepare(pQuery, false);
if(q!=NULL)
{
bool b=q->Write();
delete ((CQuery*)q);
return b;
}
else
{
return false;
}
}
void CDatabase::InsertResults(const db_nsingle_result &pResult)
{
results.push_back(pResult);
}
//ToDo: Cache Writings
void CDatabase::BeginTransaction(void)
{
Write("BEGIN IMMEDIATE;");
in_transaction=true;
}
bool CDatabase::EndTransaction(void)
{
Write("END;");
in_transaction=false;
IScopedLock lock(lock_mutex);
bool waited=false;
while(lock_count>0)
{
unlock_cond->wait(&lock);
waited=true;
}
if(waited)
{
Server->wait(1000);
}
return true;
}
IQuery* CDatabase::Prepare(std::string pQuery, bool autodestroy)
{
sqlite3_stmt *prepared_statement;
const char* tail;
int err;
bool transaction_lock=false;
while((err=sqlite3_prepare_v2(db, pQuery.c_str(), (int)pQuery.size(), &prepared_statement, &tail) )==SQLITE_LOCKED || err==SQLITE_BUSY)
{
if(err==SQLITE_LOCKED)
{
if(LockForTransaction())
{
transaction_lock=true;
if(!WaitForUnlock())
Server->Log("DATABASE DEADLOCKED in CDatabase::Prepare", LL_ERROR);
}
}
else
{
if(transaction_lock==false)
{
if(!isInTransaction() && LockForTransaction())
{
transaction_lock=true;
}
sqlite3_busy_timeout(db, 10000);
}
else
{
Server->Log("DATABASE BUSY in CDatabase::Prepare", LL_ERROR);
}
}
}
if(transaction_lock)
{
UnlockForTransaction();
sqlite3_busy_timeout(db, 50);
}
if( err!=SQLITE_OK )
{
Server->Log("Error preparing Query ["+pQuery+"]: "+sqlite3_errmsg(db),LL_ERROR);
return NULL;
}
CQuery *q=new CQuery(pQuery, prepared_statement, this);
if( autodestroy )
queries.push_back(q);
return q;
}
IQuery* CDatabase::Prepare(int id, std::string pQuery)
{
std::map<int, IQuery*>::iterator iter=prepared_queries.find(id);
if( iter!=prepared_queries.end() )
{
iter->second->Reset();
return iter->second;
}
else
{
IQuery *q=Prepare(pQuery, false);
prepared_queries.insert(std::pair<int, IQuery*>(id, q) );
return q;
}
}
void CDatabase::destroyQuery(IQuery *q)
{
for(size_t i=0;i<queries.size();++i)
{
if( queries[i]==q )
{
CQuery *cq=(CQuery*)q;
delete cq;
queries.erase( queries.begin()+i);
return;
}
}
CQuery *cq=(CQuery*)q;
delete cq;
}
void CDatabase::destroyAllQueries(void)
{
for(size_t i=0;i<queries.size();++i)
{
CQuery *cq=(CQuery*)queries[i];
delete cq;
}
queries.clear();
}
_i64 CDatabase::getLastInsertID(void)
{
return sqlite3_last_insert_rowid(db);
}
bool CDatabase::WaitForUnlock(void)
{
#ifndef BDBPLUGIN
int rc;
UnlockNotification un;
un.fired = false;
un.mutex=Server->createMutex();
un.cond=Server->createCondition();
rc = sqlite3_unlock_notify(db, unlock_notify_cb, (void *)&un);
if( rc==SQLITE_OK )
{
IScopedLock lock(un.mutex);
if( !un.fired )
{
un.cond->wait(&lock);
}
}
Server->destroy(un.mutex);
Server->destroy(un.cond);
return rc==SQLITE_OK;
#else
return false;
#endif
}
sqlite3 *CDatabase::getDatabase(void)
{
return db;
}
bool CDatabase::LockForTransaction(void)
{
lock_mutex->Lock();
++lock_count;
return true;
}
void CDatabase::UnlockForTransaction(void)
{
--lock_count;
lock_mutex->Unlock();
unlock_cond->notify_all();
}
bool CDatabase::isInTransaction(void)
{
return in_transaction;
}
bool CDatabase::Import(const std::string &pFile)
{
IFile *file=Server->openFile(pFile, MODE_READ);
if(file==NULL)
return false;
unsigned int r;
char buf[4096];
std::string query;
int state=0;
do
{
r=file->Read(buf, 4096);
for(unsigned int i=0;i<r;++i)
{
if(buf[i]==';' && state==0)
{
if(!Write(query))
return false;
query.clear();
continue;
}
else if(buf[i]=='\'' && state==0)
{
state=1;
}
else if(buf[i]=='\'' && state==1 )
{
state=0;
}
query+=buf[i];
}
}
while(r>0);
Server->destroy(file);
return true;
}
bool CDatabase::Dump(const std::string &pFile)
{
callback_data cd;
cd.db=db;
cd.out=fopen(pFile.c_str(), "wb");
if(cd.out==0)
{
return false;
}
char *cmd=new char[6];
cmd[0]='.'; cmd[1]='d'; cmd[2]='u'; cmd[3]='m'; cmd[4]='p'; cmd[5]=0;
do_meta_command(cmd, &cd);
delete []cmd;
fclose(cd.out);
return true;
}
std::string CDatabase::getEngineName(void)
{
#ifndef BDBPLUGIN
return "sqlite";
#else
return "bdb";
#endif
}
#endif