Mercurial > hg > orthanc
diff Core/SQLite/Connection.cpp @ 0:3959d33612cc
initial commit
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 19 Jul 2012 14:32:22 +0200 |
parents | |
children | db4d996ea264 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/SQLite/Connection.cpp Thu Jul 19 14:32:22 2012 +0200 @@ -0,0 +1,356 @@ +/** + * Palantir - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Belgium + * + * 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/>. + **/ + + +#include "Connection.h" + +#include <memory> +#include <cassert> +#include <sqlite3.h> +#include <string.h> + + + +namespace Palantir +{ + namespace SQLite + { + Connection::Connection() : + db_(NULL), + transactionNesting_(0), + needsRollback_(false) + { + } + + + Connection::~Connection() + { + Close(); + } + + + void Connection::CheckIsOpen() const + { + if (!db_) + { + throw PalantirException("SQLite: The database is not opened"); + } + } + + void Connection::Open(const std::string& path) + { + if (db_) + { + throw PalantirException("SQLite: Connection is already open"); + } + + int err = sqlite3_open(path.c_str(), &db_); + if (err != SQLITE_OK) + { + Close(); + db_ = NULL; + throw PalantirException("SQLite: Unable to open the database"); + } + + // Execute PRAGMAs at this point + // http://www.sqlite.org/pragma.html + Execute("PRAGMA FOREIGN_KEYS=ON;"); + + // Performance tuning + Execute("PRAGMA SYNCHRONOUS=NORMAL;"); + Execute("PRAGMA JOURNAL_MODE=WAL;"); + Execute("PRAGMA LOCKING_MODE=EXCLUSIVE;"); + Execute("PRAGMA WAL_AUTOCHECKPOINT=1000;"); + //Execute("PRAGMA TEMP_STORE=memory"); + } + + void Connection::OpenInMemory() + { + Open(":memory:"); + } + + void Connection::Close() + { + ClearCache(); + + if (db_) + { + sqlite3_close(db_); + db_ = NULL; + } + } + + void Connection::ClearCache() + { + for (CachedStatements::iterator + it = cachedStatements_.begin(); + it != cachedStatements_.end(); it++) + { + delete it->second; + } + + cachedStatements_.clear(); + } + + + StatementReference& Connection::GetCachedStatement(const StatementId& id, + const char* sql) + { + CachedStatements::iterator i = cachedStatements_.find(id); + if (i != cachedStatements_.end()) + { + if (i->second->GetReferenceCount() >= 1) + { + throw PalantirException("SQLite: This cached statement is already being referred to"); + } + + return *i->second; + } + else + { + StatementReference* statement = new StatementReference(db_, sql); + cachedStatements_[id] = statement; + return *statement; + } + } + + + bool Connection::Execute(const char* sql) + { + CheckIsOpen(); + + int error = sqlite3_exec(db_, sql, NULL, NULL, NULL); + if (error == SQLITE_ERROR) + { + throw PalantirException("SQLite Execute error: " + std::string(sqlite3_errmsg(db_))); + } + else + { + return error == SQLITE_OK; + } + } + + int Connection::ExecuteAndReturnErrorCode(const char* sql) + { + CheckIsOpen(); + return sqlite3_exec(db_, sql, NULL, NULL, NULL); + } + + // Info querying ------------------------------------------------------------- + + bool Connection::IsSQLValid(const char* sql) + { + sqlite3_stmt* stmt = NULL; + if (sqlite3_prepare_v2(db_, sql, -1, &stmt, NULL) != SQLITE_OK) + return false; + + sqlite3_finalize(stmt); + return true; + } + + bool Connection::DoesTableOrIndexExist(const char* name, + const char* type) const + { + // Our SQL is non-mutating, so this cast is OK. + Statement statement(const_cast<Connection&>(*this), + "SELECT name FROM sqlite_master WHERE type=? AND name=?"); + statement.BindString(0, type); + statement.BindString(1, name); + return statement.Step(); // Table exists if any row was returned. + } + + bool Connection::DoesTableExist(const char* table_name) const + { + return DoesTableOrIndexExist(table_name, "table"); + } + + bool Connection::DoesIndexExist(const char* index_name) const + { + return DoesTableOrIndexExist(index_name, "index"); + } + + bool Connection::DoesColumnExist(const char* table_name, const char* column_name) const + { + std::string sql("PRAGMA TABLE_INFO("); + sql.append(table_name); + sql.append(")"); + + // Our SQL is non-mutating, so this cast is OK. + Statement statement(const_cast<Connection&>(*this), sql.c_str()); + + while (statement.Step()) { + if (!statement.ColumnString(1).compare(column_name)) + return true; + } + return false; + } + + int64_t Connection::GetLastInsertRowId() const + { + return sqlite3_last_insert_rowid(db_); + } + + int Connection::GetLastChangeCount() const + { + return sqlite3_changes(db_); + } + + int Connection::GetErrorCode() const + { + return sqlite3_errcode(db_); + } + + int Connection::GetLastErrno() const + { + int err = 0; + if (SQLITE_OK != sqlite3_file_control(db_, NULL, SQLITE_LAST_ERRNO, &err)) + return -2; + + return err; + } + + const char* Connection::GetErrorMessage() const + { + return sqlite3_errmsg(db_); + } + + + bool Connection::BeginTransaction() + { + if (needsRollback_) + { + assert(transactionNesting_ > 0); + + // When we're going to rollback, fail on this begin and don't actually + // mark us as entering the nested transaction. + return false; + } + + bool success = true; + if (!transactionNesting_) + { + needsRollback_ = false; + + Statement begin(*this, SQLITE_FROM_HERE, "BEGIN TRANSACTION"); + if (!begin.Run()) + return false; + } + transactionNesting_++; + return success; + } + + void Connection::RollbackTransaction() + { + if (!transactionNesting_) + { + throw PalantirException("Rolling back a nonexistent transaction"); + } + + transactionNesting_--; + + if (transactionNesting_ > 0) + { + // Mark the outermost transaction as needing rollback. + needsRollback_ = true; + return; + } + + DoRollback(); + } + + bool Connection::CommitTransaction() + { + if (!transactionNesting_) + { + throw PalantirException("Committing a nonexistent transaction"); + } + transactionNesting_--; + + if (transactionNesting_ > 0) + { + // Mark any nested transactions as failing after we've already got one. + return !needsRollback_; + } + + if (needsRollback_) + { + DoRollback(); + return false; + } + + Statement commit(*this, SQLITE_FROM_HERE, "COMMIT"); + return commit.Run(); + } + + void Connection::DoRollback() + { + Statement rollback(*this, SQLITE_FROM_HERE, "ROLLBACK"); + rollback.Run(); + needsRollback_ = false; + } + + + + + + + static void ScalarFunctionCaller(sqlite3_context* rawContext, + int argc, + sqlite3_value** argv) + { + FunctionContext context(rawContext, argc, argv); + + void* payload = sqlite3_user_data(rawContext); + assert(payload != NULL); + + IScalarFunction& func = *(IScalarFunction*) payload; + func.Compute(context); + } + + + static void ScalarFunctionDestroyer(void* payload) + { + assert(payload != NULL); + delete (IScalarFunction*) payload; + } + + + IScalarFunction* Connection::Register(IScalarFunction* func) + { + int err = sqlite3_create_function_v2(db_, + func->GetName(), + func->GetCardinality(), + SQLITE_UTF8, + func, + ScalarFunctionCaller, + NULL, + NULL, + ScalarFunctionDestroyer); + + if (err != SQLITE_OK) + { + delete func; + throw PalantirException("SQLite: Unable to register a function"); + } + + return func; + } + + } +}