Path: blob/main/contrib/kyua/store/write_backend.cpp
39478 views
// Copyright 2011 The Kyua Authors.1// All rights reserved.2//3// Redistribution and use in source and binary forms, with or without4// modification, are permitted provided that the following conditions are5// met:6//7// * Redistributions of source code must retain the above copyright8// notice, this list of conditions and the following disclaimer.9// * Redistributions in binary form must reproduce the above copyright10// notice, this list of conditions and the following disclaimer in the11// documentation and/or other materials provided with the distribution.12// * Neither the name of Google Inc. nor the names of its contributors13// may be used to endorse or promote products derived from this software14// without specific prior written permission.15//16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.2728#include "store/write_backend.hpp"2930#include <stdexcept>3132#include "store/exceptions.hpp"33#include "store/metadata.hpp"34#include "store/read_backend.hpp"35#include "store/write_transaction.hpp"36#include "utils/env.hpp"37#include "utils/format/macros.hpp"38#include "utils/fs/path.hpp"39#include "utils/logging/macros.hpp"40#include "utils/noncopyable.hpp"41#include "utils/sanity.hpp"42#include "utils/stream.hpp"43#include "utils/sqlite/database.hpp"44#include "utils/sqlite/exceptions.hpp"45#include "utils/sqlite/statement.ipp"4647namespace fs = utils::fs;48namespace sqlite = utils::sqlite;495051/// The current schema version.52///53/// Any new database gets this schema version. Existing databases with an older54/// schema version must be first migrated to the current schema with55/// migrate_schema() before they can be used.56///57/// This must be kept in sync with the value in the corresponding schema_vX.sql58/// file, where X matches this version number.59///60/// This variable is not const to allow tests to modify it. No other code61/// should change its value.62int store::detail::current_schema_version = 3;636465namespace {666768/// Checks if a database is empty (i.e. if it is new).69///70/// \param db The database to check.71///72/// \return True if the database is empty.73static bool74empty_database(sqlite::database& db)75{76sqlite::statement stmt = db.create_statement("SELECT * FROM sqlite_master");77return !stmt.step();78}798081} // anonymous namespace828384/// Calculates the path to the schema file for the database.85///86/// \return The path to the installed schema_vX.sql file that matches the87/// current_schema_version.88fs::path89store::detail::schema_file(void)90{91return fs::path(utils::getenv_with_default("KYUA_STOREDIR", KYUA_STOREDIR))92/ (F("schema_v%s.sql") % current_schema_version);93}949596/// Initializes an empty database.97///98/// \param db The database to initialize.99///100/// \return The metadata record written into the new database.101///102/// \throw store::error If there is a problem initializing the database.103store::metadata104store::detail::initialize(sqlite::database& db)105{106PRE(empty_database(db));107108const fs::path schema = schema_file();109110LI(F("Populating new database with schema from %s") % schema);111try {112db.exec(utils::read_file(schema));113114const metadata metadata = metadata::fetch_latest(db);115LI(F("New metadata entry %s") % metadata.timestamp());116if (metadata.schema_version() != detail::current_schema_version) {117UNREACHABLE_MSG(F("current_schema_version is out of sync with "118"%s") % schema);119}120return metadata;121} catch (const store::integrity_error& e) {122// Could be raised by metadata::fetch_latest.123UNREACHABLE_MSG("Inconsistent code while creating a database");124} catch (const sqlite::error& e) {125throw error(F("Failed to initialize database: %s") % e.what());126} catch (const std::runtime_error& e) {127throw error(F("Cannot read database schema '%s'") % schema);128}129}130131132/// Internal implementation for the backend.133struct store::write_backend::impl : utils::noncopyable {134/// The SQLite database this backend talks to.135sqlite::database database;136137/// Constructor.138///139/// \param database_ The SQLite database instance.140impl(sqlite::database& database_) : database(database_)141{142}143};144145146/// Constructs a new backend.147///148/// \param pimpl_ The internal data.149store::write_backend::write_backend(impl* pimpl_) :150_pimpl(pimpl_)151{152}153154155/// Destructor.156store::write_backend::~write_backend(void)157{158}159160161/// Opens a database in read-write mode and creates it if necessary.162///163/// \param file The database file to be opened.164///165/// \return The backend representation.166///167/// \throw store::error If there is any problem opening or creating168/// the database.169store::write_backend170store::write_backend::open_rw(const fs::path& file)171{172sqlite::database db = detail::open_and_setup(173file, sqlite::open_readwrite | sqlite::open_create);174if (!empty_database(db))175throw error(F("%s already exists and is not empty; cannot open "176"for write") % file);177detail::initialize(db);178return write_backend(new impl(db));179}180181182/// Closes the SQLite database.183void184store::write_backend::close(void)185{186_pimpl->database.close();187}188189190/// Gets the connection to the SQLite database.191///192/// \return A database connection.193sqlite::database&194store::write_backend::database(void)195{196return _pimpl->database;197}198199200/// Opens a write-only transaction.201///202/// \return A new transaction.203store::write_transaction204store::write_backend::start_write(void)205{206return write_transaction(*this);207}208209210