mirror of
https://github.com/uroni/urbackup_backend.git
synced 2025-10-26 11:36:50 +00:00
524 lines
11 KiB
C++
524 lines
11 KiB
C++
/*************************************************************************
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
**************************************************************************/
|
|
#ifndef NO_SQLITE
|
|
|
|
|
|
#include "vld.h"
|
|
#include "Query.h"
|
|
#ifndef BDBPLUGIN
|
|
#include "Server.h"
|
|
#else
|
|
#include "Interface/Server.h"
|
|
#ifdef LINUX
|
|
#include "bdbplugin/config.h"
|
|
#include DB_HEADER
|
|
#else
|
|
#include <db.h>
|
|
#endif
|
|
#endif
|
|
#ifdef USE_SYSTEM_SQLITE
|
|
#include <sqlite3.h>
|
|
#else
|
|
#include "sqlite/sqlite3.h"
|
|
#endif
|
|
#include "Database.h"
|
|
#include "DatabaseCursor.h"
|
|
#include <memory.h>
|
|
#include <algorithm>
|
|
#include "stringtools.h"
|
|
|
|
|
|
#ifdef LOG_QUERIES
|
|
IMutex* CQuery::active_mutex;
|
|
std::vector<std::string> CQuery::active_queries;
|
|
#endif
|
|
|
|
CQuery::CQuery(const std::string &pStmt_str, sqlite3_stmt *prepared_statement, CDatabase *pDB)
|
|
: stmt_str(pStmt_str), cursor(NULL)
|
|
{
|
|
ps=prepared_statement;
|
|
curr_idx=1;
|
|
db=pDB;
|
|
}
|
|
|
|
CQuery::~CQuery()
|
|
{
|
|
int err=sqlite3_finalize(ps);
|
|
if( err!=SQLITE_OK && err!=SQLITE_BUSY && err!=SQLITE_IOERR_BLOCKED )
|
|
Server->Log("SQL: "+(std::string)sqlite3_errmsg(db->getDatabase())+ " Stmt: ["+stmt_str+"]", LL_ERROR);
|
|
|
|
if( err==SQLITE_IOERR )
|
|
{
|
|
Server->setFailBit(IServer::FAIL_DATABASE_IOERR);
|
|
}
|
|
|
|
if (err == SQLITE_FULL)
|
|
{
|
|
Server->setFailBit(IServer::FAIL_DATABASE_FULL);
|
|
}
|
|
|
|
delete cursor;
|
|
}
|
|
|
|
void CQuery::init_mutex(void)
|
|
{
|
|
#ifdef LOG_QUERIES
|
|
active_mutex=Server->createMutex();
|
|
#endif
|
|
}
|
|
|
|
void CQuery::Bind(const std::string &str)
|
|
{
|
|
int err=sqlite3_bind_text(ps, curr_idx, str.c_str(), (int)str.size(), SQLITE_TRANSIENT);
|
|
if( err!=SQLITE_OK )
|
|
Server->Log("Error binding text to Query Stmt: ["+stmt_str+"]", LL_ERROR);
|
|
++curr_idx;
|
|
}
|
|
|
|
void CQuery::Bind(const char* buffer, _u32 bsize)
|
|
{
|
|
int err=sqlite3_bind_blob(ps, curr_idx, buffer, bsize, SQLITE_TRANSIENT);
|
|
if( err!=SQLITE_OK )
|
|
Server->Log("Error binding blob to Query Stmt: ["+stmt_str+"]", LL_ERROR);
|
|
++curr_idx;
|
|
}
|
|
|
|
void CQuery::Bind(int p)
|
|
{
|
|
int err=sqlite3_bind_int(ps, curr_idx, p);
|
|
if( err!=SQLITE_OK )
|
|
Server->Log("Error binding int to Query Stmt: ["+stmt_str+"]", LL_ERROR);
|
|
++curr_idx;
|
|
}
|
|
|
|
void CQuery::Bind(unsigned int p)
|
|
{
|
|
Bind((int64)p);
|
|
}
|
|
|
|
void CQuery::Bind(double p)
|
|
{
|
|
int err=sqlite3_bind_double(ps, curr_idx, p);
|
|
if( err!=SQLITE_OK )
|
|
Server->Log("Error binding double to Query Stmt: ["+stmt_str+"]", LL_ERROR);
|
|
++curr_idx;
|
|
}
|
|
|
|
void CQuery::Bind(int64 p)
|
|
{
|
|
int err=sqlite3_bind_int64(ps, curr_idx, p);
|
|
if( err!=SQLITE_OK )
|
|
Server->Log("Error binding int64 to Query Stmt: ["+stmt_str+"]", LL_ERROR);
|
|
++curr_idx;
|
|
}
|
|
|
|
#if defined(_WIN64) || defined(_LP64)
|
|
void CQuery::Bind(size_t p)
|
|
{
|
|
Bind((int64)p);
|
|
}
|
|
#endif
|
|
|
|
void CQuery::Reset(void)
|
|
{
|
|
sqlite3_reset(ps);
|
|
//sqlite3_clear_bindings(ps);
|
|
curr_idx=1;
|
|
}
|
|
|
|
bool CQuery::Write(int timeoutms)
|
|
{
|
|
IScopedReadLock lock(db->getSingleUseMutex());
|
|
|
|
#ifdef LOG_WRITE_QUERIES
|
|
ScopedAddActiveQuery active_query(this);
|
|
#endif
|
|
return Execute(timeoutms);
|
|
}
|
|
|
|
bool CQuery::Execute(int timeoutms)
|
|
{
|
|
//Server->Log("Write: "+stmt_str);
|
|
|
|
bool transaction_lock=false;
|
|
int tries=60; //10min
|
|
if(timeoutms>=0)
|
|
{
|
|
sqlite3_busy_timeout(db->getDatabase(), timeoutms);
|
|
}
|
|
int err=sqlite3_step(ps);
|
|
while( err==SQLITE_IOERR_BLOCKED
|
|
|| err==SQLITE_BUSY
|
|
|| err==SQLITE_PROTOCOL
|
|
|| err==SQLITE_ROW
|
|
|| err==SQLITE_LOCKED )
|
|
{
|
|
if(err==SQLITE_IOERR_BLOCKED)
|
|
{
|
|
Server->Log("SQlite is blocked!", LL_ERROR);
|
|
}
|
|
if(err==SQLITE_BUSY
|
|
|| err==SQLITE_IOERR_BLOCKED
|
|
|| err==SQLITE_PROTOCOL )
|
|
{
|
|
if(timeoutms>=0)
|
|
{
|
|
break;
|
|
}
|
|
else if(!db->isInTransaction() && !transaction_lock)
|
|
{
|
|
sqlite3_reset(ps);
|
|
if(db->LockForTransaction())
|
|
{
|
|
Server->Log("LockForTransaction in CQuery::Execute Stmt: ["+stmt_str+"]", LL_DEBUG);
|
|
transaction_lock=true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sqlite3_reset(ps);
|
|
--tries;
|
|
if(tries==-1)
|
|
{
|
|
Server->Log("SQLITE: Long running query Stmt: ["+stmt_str+"]", LL_ERROR);
|
|
showActiveQueries(LL_ERROR);
|
|
}
|
|
else if(tries>=0)
|
|
{
|
|
Server->Log("SQLITE_BUSY in CQuery::Execute Stmt: ["+stmt_str+"]", LL_INFO);
|
|
showActiveQueries(LL_INFO);
|
|
}
|
|
}
|
|
}
|
|
else if(err==SQLITE_LOCKED)
|
|
{
|
|
if(transaction_lock)
|
|
{
|
|
db->UnlockForTransaction();
|
|
transaction_lock=false;
|
|
Server->Log("UnlockForTransaction in CQuery::Execute after SQLITE_LOCKED Stmt: ["+stmt_str+"]", LL_DEBUG);
|
|
}
|
|
if(!db->WaitForUnlock())
|
|
{
|
|
Server->Log("DEADLOCK in CQuery::Execute Stmt: ["+stmt_str+"]", LL_ERROR);
|
|
showActiveQueries(LL_ERROR);
|
|
Server->wait(1000);
|
|
if(timeoutms>=0)
|
|
{
|
|
timeoutms-=1000;
|
|
|
|
if(timeoutms<=0)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
err=sqlite3_step(ps);
|
|
}
|
|
|
|
if(transaction_lock)
|
|
{
|
|
db->UnlockForTransaction();
|
|
}
|
|
|
|
if(timeoutms>=0)
|
|
{
|
|
sqlite3_busy_timeout(db->getDatabase(), c_sqlite_busy_timeout_default);
|
|
}
|
|
|
|
//Server->Log("Write done: "+stmt_str);
|
|
|
|
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);
|
|
}
|
|
|
|
if( err!=SQLITE_DONE )
|
|
{
|
|
if(timeoutms<0)
|
|
{
|
|
Server->Log("Error in CQuery::Execute - "+(std::string)sqlite3_errmsg(db->getDatabase()) +" Stmt: ["+stmt_str+"]", LL_ERROR);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CQuery::setupStepping(int *timeoutms, bool with_read_lock)
|
|
{
|
|
if (with_read_lock)
|
|
{
|
|
single_use_lock.reset(new IScopedReadLock(db->getSingleUseMutex()));
|
|
}
|
|
|
|
if(timeoutms!=NULL && *timeoutms>=0)
|
|
{
|
|
sqlite3_busy_timeout(db->getDatabase(), *timeoutms);
|
|
}
|
|
}
|
|
|
|
void CQuery::shutdownStepping(int err, int *timeoutms, bool& transaction_lock)
|
|
{
|
|
if(timeoutms!=NULL && *timeoutms>=0)
|
|
{
|
|
sqlite3_busy_timeout(db->getDatabase(), c_sqlite_busy_timeout_default);
|
|
}
|
|
|
|
if (err == SQLITE_ROW)
|
|
{
|
|
sqlite3_reset(ps);
|
|
}
|
|
|
|
if(timeoutms!=NULL)
|
|
{
|
|
if(err!=SQLITE_DONE)
|
|
{
|
|
*timeoutms=1;
|
|
}
|
|
else
|
|
{
|
|
*timeoutms=0;
|
|
}
|
|
}
|
|
|
|
if(transaction_lock)
|
|
{
|
|
db->UnlockForTransaction();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
single_use_lock.reset();
|
|
}
|
|
|
|
db_results CQuery::Read(int *timeoutms)
|
|
{
|
|
IScopedReadLock lock(db->getSingleUseMutex());
|
|
|
|
int err;
|
|
db_results rows;
|
|
|
|
bool transaction_lock=false;
|
|
int tries=60; //10min
|
|
|
|
#ifdef LOG_READ_QUERIES
|
|
ScopedAddActiveQuery active_query(this);
|
|
#endif
|
|
|
|
setupStepping(timeoutms, false);
|
|
|
|
db_single_result res;
|
|
do
|
|
{
|
|
bool reset=false;
|
|
err=step(res, timeoutms, tries, transaction_lock, reset);
|
|
if(reset)
|
|
{
|
|
rows.clear();
|
|
}
|
|
if(err==SQLITE_ROW)
|
|
{
|
|
rows.push_back(res);
|
|
res.clear();
|
|
}
|
|
}
|
|
while(resultOkay(err));
|
|
|
|
shutdownStepping(err, timeoutms, transaction_lock);
|
|
|
|
return rows;
|
|
}
|
|
|
|
bool CQuery::resultOkay(int rc)
|
|
{
|
|
return rc==SQLITE_BUSY ||
|
|
rc==SQLITE_PROTOCOL ||
|
|
rc==SQLITE_ROW ||
|
|
rc==SQLITE_IOERR_BLOCKED;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
std::string ustring_sqlite3_column_name(sqlite3_stmt* ps, int N)
|
|
{
|
|
const char* c_name = sqlite3_column_name(ps, N);
|
|
if(c_name==NULL)
|
|
{
|
|
return std::string();
|
|
}
|
|
|
|
return std::string(c_name);
|
|
}
|
|
}
|
|
|
|
int CQuery::step(db_single_result& res, int *timeoutms, int& tries, bool& transaction_lock, bool& reset)
|
|
{
|
|
int err=sqlite3_step(ps);
|
|
if( resultOkay(err) )
|
|
{
|
|
if( err==SQLITE_BUSY
|
|
|| err==SQLITE_PROTOCOL
|
|
|| err==SQLITE_IOERR_BLOCKED )
|
|
{
|
|
if(timeoutms!=NULL && *timeoutms>=0)
|
|
{
|
|
return SQLITE_ABORT;
|
|
}
|
|
else if(!db->isInTransaction() && !transaction_lock)
|
|
{
|
|
sqlite3_reset(ps);
|
|
reset=true;
|
|
if(db->LockForTransaction())
|
|
{
|
|
Server->Log("LockForTransaction in CQuery::Read Stmt: ["+stmt_str+"]", LL_DEBUG);
|
|
transaction_lock=true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sqlite3_reset(ps);
|
|
reset=true;
|
|
--tries;
|
|
if(tries==-1)
|
|
{
|
|
Server->Log("SQLITE: Long runnning query Stmt: ["+stmt_str+"]", LL_ERROR);
|
|
showActiveQueries(LL_ERROR);
|
|
}
|
|
else
|
|
{
|
|
Server->Log("SQLITE_BUSY in CQuery::Read Stmt: ["+stmt_str+"]", LL_INFO);
|
|
showActiveQueries(LL_INFO);
|
|
}
|
|
}
|
|
}
|
|
else if( err==SQLITE_ROW )
|
|
{
|
|
int column=0;
|
|
std::string column_name;
|
|
while( !(column_name=ustring_sqlite3_column_name(ps, column) ).empty() )
|
|
{
|
|
const void* data;
|
|
int data_size;
|
|
if(sqlite3_column_type(ps, column)==SQLITE_BLOB)
|
|
{
|
|
data = sqlite3_column_blob(ps, column);
|
|
data_size =sqlite3_column_bytes(ps, column);
|
|
}
|
|
else
|
|
{
|
|
data = sqlite3_column_text(ps, column);
|
|
data_size = sqlite3_column_bytes(ps, column);
|
|
}
|
|
std::string datastr(reinterpret_cast<const char*>(data), reinterpret_cast<const char*>(data)+data_size);
|
|
res.insert( std::pair<std::string, std::string>(column_name, datastr) );
|
|
++column;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Server->wait(1000);
|
|
if(timeoutms!=NULL && *timeoutms>=0)
|
|
{
|
|
*timeoutms-=1000;
|
|
|
|
if(*timeoutms<=0)
|
|
{
|
|
return SQLITE_ABORT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return err;
|
|
}
|
|
|
|
IDatabaseCursor* CQuery::Cursor(int *timeoutms)
|
|
{
|
|
if(cursor==NULL)
|
|
{
|
|
cursor=new DatabaseCursor(this, timeoutms);
|
|
}
|
|
else
|
|
{
|
|
if (!cursor->reset())
|
|
return NULL;
|
|
}
|
|
|
|
return cursor;
|
|
}
|
|
|
|
std::string CQuery::getStatement(void)
|
|
{
|
|
return stmt_str;
|
|
}
|
|
|
|
std::string CQuery::getErrMsg(void)
|
|
{
|
|
return std::string(sqlite3_errmsg(db->getDatabase()));
|
|
}
|
|
|
|
void CQuery::addActiveQuery(const std::string& query_str)
|
|
{
|
|
#ifdef LOG_QUERIES
|
|
IScopedLock lock(active_mutex);
|
|
active_queries.push_back(query_str);
|
|
#endif
|
|
}
|
|
|
|
void CQuery::removeActiveQuery(const std::string& query_str)
|
|
{
|
|
#ifdef LOG_QUERIES
|
|
IScopedLock lock(active_mutex);
|
|
std::vector<std::string>::iterator iter =
|
|
std::find(active_queries.begin(), active_queries.end(),
|
|
query_str);
|
|
if(iter!=active_queries.end())
|
|
active_queries.erase(iter);
|
|
#endif
|
|
}
|
|
|
|
void CQuery::showActiveQueries(int loglevel)
|
|
{
|
|
#ifdef LOG_QUERIES
|
|
IScopedLock lock(active_mutex);
|
|
for(size_t i=0;i<active_queries.size();++i)
|
|
{
|
|
Server->Log("Active query("+convert(i)+"): "+active_queries[i], loglevel);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#endif |