/*************************************************************************
* UrBackup - Client/Server backup system
* Copyright (C) 2011-2016 Martin Raiber
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
**************************************************************************/
#ifndef NO_SQLITE
#if defined(_WIN32) || defined(WIN32)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "vld.h"
#ifndef BDBPLUGIN
#include "Server.h"
#else
#ifdef LINUX
#include "bdbplugin/config.h"
#include DB_HEADER
#else
#include
#endif
#include "Interface/Server.h"
#endif
#include "Query.h"
#ifdef USE_SYSTEM_SQLITE
#include
#else
#include "sqlite/sqlite3.h"
#endif
#include "Interface/File.h"
#include
extern "C"
{
#include "sqlite/shell.h"
}
#include "Database.h"
#include "stringtools.h"
namespace
{
size_t get_sqlite_cache_size()
{
std::string cache_size_str = Server->getServerParameter("sqlite_cache_size");
if(!cache_size_str.empty())
{
return atoi(cache_size_str.c_str());
}
else
{
return 2*1024; //2MB
}
}
void errorLogCallback(void *pArg, int iErrCode, const char *zMsg)
{
switch (iErrCode)
{
case SQLITE_LOCKED:
case SQLITE_BUSY:
case SQLITE_SCHEMA:
return;
case SQLITE_NOTICE_RECOVER_ROLLBACK:
case SQLITE_NOTICE_RECOVER_WAL:
Server->Log("SQLite: "+ std::string(zMsg) + " code: " + convert(iErrCode), LL_INFO);
break;
default:
Server->Log("SQLite: " + std::string(zMsg) + " errorcode: " + convert(iErrCode), LL_WARNING);
break;
}
}
}
struct UnlockNotification {
bool fired;
ICondition* cond;
IMutex *mutex;
};
static void unlock_notify_cb(void **apArg, int nArg)
{
for(int i=0; imutex);
p->fired = true;
p->cond->notify_all();
}
}
CDatabase::~CDatabase()
{
destroyAllQueries();
for(std::map::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, const std::vector > &attach,
size_t allocation_chunk_size, ISharedMutex* p_single_user_mutex, IMutex* p_lock_mutex,
int* p_lock_count, ICondition *p_unlock_cond, const str_map& p_params)
{
single_user_mutex = p_single_user_mutex;
lock_mutex = p_lock_mutex;
lock_count = p_lock_count;
unlock_cond = p_unlock_cond;
params = p_params;
attached_dbs=attach;
in_transaction=false;
if( sqlite3_open(pFile.c_str(), &db) )
{
Server->Log("Could not open db ["+pFile+"]");
sqlite3_close(db);
db = NULL;
return false;
}
else
{
str_map::const_iterator it = params.find("synchronous");
if (it != params.end())
{
Write("PRAGMA synchronous="+it->second);
}
else
{
Write("PRAGMA synchronous=NORMAL");
}
Write("PRAGMA foreign_keys = ON");
Write("PRAGMA threads = 2");
it = params.find("wal_autocheckpoint");
if (it != params.end())
{
Write("PRAGMA wal_autocheckpoint=" + it->second);
if (watoi(it->second)<=0)
{
int enable = 1;
sqlite3_file_control(db, NULL, SQLITE_FCNTL_PERSIST_WAL, &enable);
#ifdef SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE
int was_enabled = 0;
sqlite3_db_config(db, SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, 1, &was_enabled);
#endif
}
}
it = params.find("page_size");
if (it != params.end())
{
Write("PRAGMA page_size=" + it->second);
}
else
{
Write("PRAGMA page_size=4096");
}
it = params.find("mmap_size");
if (it != params.end())
{
Write("PRAGMA mmap_size=" + it->second);
}
if(allocation_chunk_size!=std::string::npos)
{
int chunk_size = static_cast(allocation_chunk_size);
sqlite3_file_control(db, NULL, SQLITE_FCNTL_CHUNK_SIZE, &chunk_size);
}
static size_t sqlite_cache_size = get_sqlite_cache_size();
Write("PRAGMA cache_size = -"+convert(sqlite_cache_size));
sqlite3_busy_timeout(db, c_sqlite_busy_timeout_default);
#if defined(_DEBUG) || (!defined(_WIN32) && !defined(NDEBUG))
if (Server->getRandomNumber() % 2 == 0)
{
Write("PRAGMA reverse_unordered_selects = ON");
}
#endif
AttachDBs();
return true;
}
}
void CDatabase::initMutex(void)
{
sqlite3_config(SQLITE_CONFIG_LOG, errorLogCallback, NULL);
}
void CDatabase::destroyMutex(void)
{
}
db_results CDatabase::Read(std::string pQuery)
{
//Server->Log("SQL Query(Read): "+pQuery, LL_DEBUG);
IQuery *q=Prepare(pQuery, false);
if(q!=NULL)
{
db_results ret=q->Read();
delete ((CQuery*)q);
return ret;
}
return db_results();
}
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;
}
}
//ToDo: Cache Writings
bool CDatabase::BeginReadTransaction()
{
if (write_lock.get() == NULL)
{
transaction_read_lock.reset(new IScopedReadLock(single_user_mutex));
}
in_transaction = true;
if(Write("BEGIN"))
{
return true;
}
else
{
in_transaction = false;
return false;
}
}
bool CDatabase::BeginWriteTransaction()
{
if (write_lock.get() == NULL)
{
transaction_read_lock.reset(new IScopedReadLock(single_user_mutex));
}
in_transaction = true;
if(Write("BEGIN IMMEDIATE;"))
{
return true;
}
else
{
in_transaction = false;
return false;
}
}
bool CDatabase::EndTransaction(void)
{
bool ret = Write("END;");
in_transaction=false;
transaction_read_lock.reset();
IScopedLock lock(lock_mutex);
bool waited=false;
while(*lock_count>0)
{
unlock_cond->wait(&lock);
waited=true;
}
if(waited)
{
Server->wait(50);
}
return ret;
}
bool CDatabase::RollbackTransaction()
{
bool ret = Write("ROLLBACK;");
in_transaction = false;
transaction_read_lock.reset();
IScopedLock lock(lock_mutex);
bool waited = false;
while (*lock_count>0)
{
unlock_cond->wait(&lock);
waited = true;
}
if (waited)
{
Server->wait(50);
}
return ret;
}
IQuery* CDatabase::Prepare(std::string pQuery, bool autodestroy)
{
IScopedReadLock lock(NULL);
if (!in_transaction && write_lock.get()==NULL)
{
lock.relock(single_user_mutex);
}
int prepare_tries = 0;
#ifdef SQLITE_PREPARE_RETRIES
prepare_tries = SQLITE_PREPARE_RETRIES;
#endif
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
|| err==SQLITE_PROTOCOL
|| (err!=SQLITE_OK && prepare_tries>0) )
{
--prepare_tries;
if(err==SQLITE_LOCKED)
{
if(!transaction_lock && LockForTransaction())
{
transaction_lock=true;
if(!WaitForUnlock())
Server->Log("DATABASE DEADLOCKED in CDatabase::Prepare", LL_ERROR);
}
}
else if(err== SQLITE_BUSY
|| err==SQLITE_PROTOCOL)
{
if(!transaction_lock)
{
if(!isInTransaction() && LockForTransaction())
{
transaction_lock=true;
}
sqlite3_busy_timeout(db, 10000);
}
else
{
Server->Log("DATABASE BUSY in CDatabase::Prepare", LL_ERROR);
}
}
else
{
Server->Log("Error preparing Query [" + pQuery + "]: " + sqlite3_errmsg(db)+". Retrying in 1s...", LL_ERROR);
Server->wait(1000);
}
}
if(transaction_lock)
{
UnlockForTransaction();
sqlite3_busy_timeout(db, 50);
}
if( err!=SQLITE_OK )
{
Server->Log("Error preparing Query ["+pQuery+"]: "+sqlite3_errmsg(db),LL_ERROR);
if(err==SQLITE_IOERR)
{
Server->setFailBit(IServer::FAIL_DATABASE_IOERR);
}
if(err==SQLITE_CORRUPT)
{
Server->setFailBit(IServer::FAIL_DATABASE_CORRUPTED);
}
if (err ==SQLITE_FULL)
{
Server->setFailBit(IServer::FAIL_DATABASE_FULL);
}
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)
{
IScopedReadLock lock(NULL);
if (!in_transaction && write_lock.get()==NULL)
{
lock.relock(single_user_mutex);
}
std::map::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(id, q) );
return q;
}
}
void CDatabase::destroyQuery(IQuery *q)
{
if(q==NULL)
{
return;
}
for(size_t i=0;icreateMutex();
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;
unlock_cond->notify_all();
lock_mutex->Unlock();
}
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;i0);
Server->destroy(file);
return true;
}
bool CDatabase::Dump(const std::string &pFile)
{
const char* db_fn = sqlite3_db_filename(db, NULL);
if (db_fn == NULL)
return false;
ShellState cd = {};
cd.openMode = 1;
cd.zDbFilename = db_fn;
cd.out=fopen(pFile.c_str(), "wb");
if(cd.out==0)
{
return false;
}
std::string cmd = ".dump";
int rc = do_meta_command_r(&cmd[0], &cd);
fclose(cd.out);
if (cd.db != 0)
sqlite3_close(cd.db);
return rc == SQLITE_OK;
}
bool CDatabase::Recover(const std::string & pFile)
{
const char* db_fn = sqlite3_db_filename(db, NULL);
if (db_fn == NULL)
return false;
ShellState cd = {};
cd.openMode = 1;
cd.zDbFilename = db_fn;
cd.out = fopen(pFile.c_str(), "wb");
if (cd.out == 0)
{
return false;
}
std::string cmd = ".recover";
int rc = do_meta_command_r(&cmd[0], &cd);
fclose(cd.out);
if (cd.db != 0)
sqlite3_close(cd.db);
return rc==SQLITE_OK;
}
std::string CDatabase::getEngineName(void)
{
#ifndef BDBPLUGIN
return "sqlite";
#else
return "bdb";
#endif
}
void CDatabase::AttachDBs(void)
{
for(size_t i=0;isecond);
}
else
{
Write("PRAGMA "+ attached_dbs[i].second+".synchronous=NORMAL");
}
}
}
void CDatabase::DetachDBs(void)
{
for(size_t i=0;ibackupProgress(done*page_size, total*page_size);
} while( rc==SQLITE_OK || rc==SQLITE_BUSY || rc==SQLITE_PROTOCOL || rc==SQLITE_LOCKED );
/* Release resources allocated by backup_init(). */
(void)sqlite3_backup_finish(pBackup);
}
else
{
Server->Log("Opening backup connection failed", LL_ERROR);
}
rc = sqlite3_errcode(pBackupDB);
if(rc!=0)
{
Server->Log("Database backup failed with error code: "+convert(rc)+" err: "+sqlite3_errmsg(pBackupDB), LL_ERROR);
}
}
/* Close the database connection opened on database file zFilename
** and return the result of this function. */
(void)sqlite3_close(pBackupDB);
return rc==0;
}
bool CDatabase::Backup(const std::string &pFile, IBackupProgress* progress)
{
std::string path=ExtractFilePath(pFile);
bool b=backup_db(pFile, "main", progress);
if(!b)
return false;
for(size_t i=0;i