xref: /minix3/external/bsd/kyua-cli/dist/store/backend.cpp (revision 11be35a165022172ed3cea20f2b5df0307540b0e)
1*11be35a1SLionel Sambuc // Copyright 2011 Google Inc.
2*11be35a1SLionel Sambuc // All rights reserved.
3*11be35a1SLionel Sambuc //
4*11be35a1SLionel Sambuc // Redistribution and use in source and binary forms, with or without
5*11be35a1SLionel Sambuc // modification, are permitted provided that the following conditions are
6*11be35a1SLionel Sambuc // met:
7*11be35a1SLionel Sambuc //
8*11be35a1SLionel Sambuc // * Redistributions of source code must retain the above copyright
9*11be35a1SLionel Sambuc //   notice, this list of conditions and the following disclaimer.
10*11be35a1SLionel Sambuc // * Redistributions in binary form must reproduce the above copyright
11*11be35a1SLionel Sambuc //   notice, this list of conditions and the following disclaimer in the
12*11be35a1SLionel Sambuc //   documentation and/or other materials provided with the distribution.
13*11be35a1SLionel Sambuc // * Neither the name of Google Inc. nor the names of its contributors
14*11be35a1SLionel Sambuc //   may be used to endorse or promote products derived from this software
15*11be35a1SLionel Sambuc //   without specific prior written permission.
16*11be35a1SLionel Sambuc //
17*11be35a1SLionel Sambuc // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18*11be35a1SLionel Sambuc // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19*11be35a1SLionel Sambuc // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20*11be35a1SLionel Sambuc // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21*11be35a1SLionel Sambuc // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22*11be35a1SLionel Sambuc // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23*11be35a1SLionel Sambuc // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24*11be35a1SLionel Sambuc // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25*11be35a1SLionel Sambuc // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26*11be35a1SLionel Sambuc // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27*11be35a1SLionel Sambuc // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28*11be35a1SLionel Sambuc 
29*11be35a1SLionel Sambuc #include "store/backend.hpp"
30*11be35a1SLionel Sambuc 
31*11be35a1SLionel Sambuc #include <fstream>
32*11be35a1SLionel Sambuc 
33*11be35a1SLionel Sambuc #include "store/exceptions.hpp"
34*11be35a1SLionel Sambuc #include "store/metadata.hpp"
35*11be35a1SLionel Sambuc #include "store/transaction.hpp"
36*11be35a1SLionel Sambuc #include "utils/env.hpp"
37*11be35a1SLionel Sambuc #include "utils/format/macros.hpp"
38*11be35a1SLionel Sambuc #include "utils/logging/macros.hpp"
39*11be35a1SLionel Sambuc #include "utils/sanity.hpp"
40*11be35a1SLionel Sambuc #include "utils/stream.hpp"
41*11be35a1SLionel Sambuc #include "utils/sqlite/database.hpp"
42*11be35a1SLionel Sambuc #include "utils/sqlite/exceptions.hpp"
43*11be35a1SLionel Sambuc #include "utils/sqlite/statement.ipp"
44*11be35a1SLionel Sambuc 
45*11be35a1SLionel Sambuc namespace fs = utils::fs;
46*11be35a1SLionel Sambuc namespace sqlite = utils::sqlite;
47*11be35a1SLionel Sambuc 
48*11be35a1SLionel Sambuc 
49*11be35a1SLionel Sambuc /// The current schema version.
50*11be35a1SLionel Sambuc ///
51*11be35a1SLionel Sambuc /// Any new database gets this schema version.  Existing databases with an older
52*11be35a1SLionel Sambuc /// schema version must be first migrated to the current schema with
53*11be35a1SLionel Sambuc /// migrate_schema() before they can be used.
54*11be35a1SLionel Sambuc ///
55*11be35a1SLionel Sambuc /// This must be kept in sync with the value in the corresponding schema_vX.sql
56*11be35a1SLionel Sambuc /// file, where X matches this version number.
57*11be35a1SLionel Sambuc ///
58*11be35a1SLionel Sambuc /// This variable is not const to allow tests to modify it.  No other code
59*11be35a1SLionel Sambuc /// should change its value.
60*11be35a1SLionel Sambuc int store::detail::current_schema_version = 2;
61*11be35a1SLionel Sambuc 
62*11be35a1SLionel Sambuc 
63*11be35a1SLionel Sambuc namespace {
64*11be35a1SLionel Sambuc 
65*11be35a1SLionel Sambuc 
66*11be35a1SLionel Sambuc /// Opens a database and defines session pragmas.
67*11be35a1SLionel Sambuc ///
68*11be35a1SLionel Sambuc /// This auxiliary function ensures that, every time we open a SQLite database,
69*11be35a1SLionel Sambuc /// we define the same set of pragmas for it.
70*11be35a1SLionel Sambuc ///
71*11be35a1SLionel Sambuc /// \param file The database file to be opened.
72*11be35a1SLionel Sambuc /// \param flags The flags for the open; see sqlite::database::open.
73*11be35a1SLionel Sambuc ///
74*11be35a1SLionel Sambuc /// \return The opened database.
75*11be35a1SLionel Sambuc ///
76*11be35a1SLionel Sambuc /// \throw store::error If there is a problem opening or creating the database.
77*11be35a1SLionel Sambuc static sqlite::database
do_open(const fs::path & file,const int flags)78*11be35a1SLionel Sambuc do_open(const fs::path& file, const int flags)
79*11be35a1SLionel Sambuc {
80*11be35a1SLionel Sambuc     try {
81*11be35a1SLionel Sambuc         sqlite::database database = sqlite::database::open(file, flags);
82*11be35a1SLionel Sambuc         database.exec("PRAGMA foreign_keys = ON");
83*11be35a1SLionel Sambuc         return database;
84*11be35a1SLionel Sambuc     } catch (const sqlite::error& e) {
85*11be35a1SLionel Sambuc         throw store::error(F("Cannot open '%s': %s") % file % e.what());
86*11be35a1SLionel Sambuc     }
87*11be35a1SLionel Sambuc }
88*11be35a1SLionel Sambuc 
89*11be35a1SLionel Sambuc 
90*11be35a1SLionel Sambuc /// Checks if a database is empty (i.e. if it is new).
91*11be35a1SLionel Sambuc ///
92*11be35a1SLionel Sambuc /// \param db The database to check.
93*11be35a1SLionel Sambuc ///
94*11be35a1SLionel Sambuc /// \return True if the database is empty.
95*11be35a1SLionel Sambuc static bool
empty_database(sqlite::database & db)96*11be35a1SLionel Sambuc empty_database(sqlite::database& db)
97*11be35a1SLionel Sambuc {
98*11be35a1SLionel Sambuc     sqlite::statement stmt = db.create_statement("SELECT * FROM sqlite_master");
99*11be35a1SLionel Sambuc     return !stmt.step();
100*11be35a1SLionel Sambuc }
101*11be35a1SLionel Sambuc 
102*11be35a1SLionel Sambuc 
103*11be35a1SLionel Sambuc /// Performs a single migration step.
104*11be35a1SLionel Sambuc ///
105*11be35a1SLionel Sambuc /// \param db Open database to which to apply the migration step.
106*11be35a1SLionel Sambuc /// \param version_from Current schema version in the database.
107*11be35a1SLionel Sambuc /// \param version_to Schema version to migrate to.
108*11be35a1SLionel Sambuc ///
109*11be35a1SLionel Sambuc /// \throw error If there is a problem applying the migration.
110*11be35a1SLionel Sambuc static void
migrate_schema_step(sqlite::database & db,const int version_from,const int version_to)111*11be35a1SLionel Sambuc migrate_schema_step(sqlite::database& db, const int version_from,
112*11be35a1SLionel Sambuc                     const int version_to)
113*11be35a1SLionel Sambuc {
114*11be35a1SLionel Sambuc     PRE(version_to == version_from + 1);
115*11be35a1SLionel Sambuc 
116*11be35a1SLionel Sambuc     const fs::path migration = store::detail::migration_file(version_from,
117*11be35a1SLionel Sambuc                                                              version_to);
118*11be35a1SLionel Sambuc 
119*11be35a1SLionel Sambuc     std::ifstream input(migration.c_str());
120*11be35a1SLionel Sambuc     if (!input)
121*11be35a1SLionel Sambuc         throw store::error(F("Cannot open migration file '%s'") % migration);
122*11be35a1SLionel Sambuc 
123*11be35a1SLionel Sambuc     const std::string migration_string = utils::read_stream(input);
124*11be35a1SLionel Sambuc     try {
125*11be35a1SLionel Sambuc         db.exec(migration_string);
126*11be35a1SLionel Sambuc     } catch (const sqlite::error& e) {
127*11be35a1SLionel Sambuc         throw store::error(F("Schema migration failed: %s") % e.what());
128*11be35a1SLionel Sambuc     }
129*11be35a1SLionel Sambuc }
130*11be35a1SLionel Sambuc 
131*11be35a1SLionel Sambuc 
132*11be35a1SLionel Sambuc }  // anonymous namespace
133*11be35a1SLionel Sambuc 
134*11be35a1SLionel Sambuc 
135*11be35a1SLionel Sambuc /// Calculates the path to a schema migration file.
136*11be35a1SLionel Sambuc ///
137*11be35a1SLionel Sambuc /// \param version_from The version from which the database is being upgraded.
138*11be35a1SLionel Sambuc /// \param version_to The version to which the database is being upgraded.
139*11be35a1SLionel Sambuc ///
140*11be35a1SLionel Sambuc /// \return The path to the installed migrate_vX_vY.sql file.
141*11be35a1SLionel Sambuc fs::path
migration_file(const int version_from,const int version_to)142*11be35a1SLionel Sambuc store::detail::migration_file(const int version_from, const int version_to)
143*11be35a1SLionel Sambuc {
144*11be35a1SLionel Sambuc     return fs::path(utils::getenv_with_default("KYUA_STOREDIR", KYUA_STOREDIR))
145*11be35a1SLionel Sambuc         / (F("migrate_v%s_v%s.sql") % version_from % version_to);
146*11be35a1SLionel Sambuc }
147*11be35a1SLionel Sambuc 
148*11be35a1SLionel Sambuc 
149*11be35a1SLionel Sambuc /// Calculates the path to the schema file for the database.
150*11be35a1SLionel Sambuc ///
151*11be35a1SLionel Sambuc /// \return The path to the installed schema_vX.sql file that matches the
152*11be35a1SLionel Sambuc /// current_schema_version.
153*11be35a1SLionel Sambuc fs::path
schema_file(void)154*11be35a1SLionel Sambuc store::detail::schema_file(void)
155*11be35a1SLionel Sambuc {
156*11be35a1SLionel Sambuc     return fs::path(utils::getenv_with_default("KYUA_STOREDIR", KYUA_STOREDIR))
157*11be35a1SLionel Sambuc         / (F("schema_v%s.sql") % current_schema_version);
158*11be35a1SLionel Sambuc }
159*11be35a1SLionel Sambuc 
160*11be35a1SLionel Sambuc 
161*11be35a1SLionel Sambuc /// Initializes an empty database.
162*11be35a1SLionel Sambuc ///
163*11be35a1SLionel Sambuc /// \param db The database to initialize.
164*11be35a1SLionel Sambuc ///
165*11be35a1SLionel Sambuc /// \return The metadata record written into the new database.
166*11be35a1SLionel Sambuc ///
167*11be35a1SLionel Sambuc /// \throw store::error If there is a problem initializing the database.
168*11be35a1SLionel Sambuc store::metadata
initialize(sqlite::database & db)169*11be35a1SLionel Sambuc store::detail::initialize(sqlite::database& db)
170*11be35a1SLionel Sambuc {
171*11be35a1SLionel Sambuc     PRE(empty_database(db));
172*11be35a1SLionel Sambuc 
173*11be35a1SLionel Sambuc     const fs::path schema = schema_file();
174*11be35a1SLionel Sambuc 
175*11be35a1SLionel Sambuc     std::ifstream input(schema.c_str());
176*11be35a1SLionel Sambuc     if (!input)
177*11be35a1SLionel Sambuc         throw error(F("Cannot open database schema '%s'") % schema);
178*11be35a1SLionel Sambuc 
179*11be35a1SLionel Sambuc     LI(F("Populating new database with schema from %s") % schema);
180*11be35a1SLionel Sambuc     const std::string schema_string = utils::read_stream(input);
181*11be35a1SLionel Sambuc     try {
182*11be35a1SLionel Sambuc         db.exec(schema_string);
183*11be35a1SLionel Sambuc 
184*11be35a1SLionel Sambuc         const metadata metadata = metadata::fetch_latest(db);
185*11be35a1SLionel Sambuc         LI(F("New metadata entry %s") % metadata.timestamp());
186*11be35a1SLionel Sambuc         if (metadata.schema_version() != detail::current_schema_version) {
187*11be35a1SLionel Sambuc             UNREACHABLE_MSG(F("current_schema_version is out of sync with "
188*11be35a1SLionel Sambuc                               "%s") % schema);
189*11be35a1SLionel Sambuc         }
190*11be35a1SLionel Sambuc         return metadata;
191*11be35a1SLionel Sambuc     } catch (const store::integrity_error& e) {
192*11be35a1SLionel Sambuc         // Could be raised by metadata::fetch_latest.
193*11be35a1SLionel Sambuc         UNREACHABLE_MSG("Inconsistent code while creating a database");
194*11be35a1SLionel Sambuc     } catch (const sqlite::error& e) {
195*11be35a1SLionel Sambuc         throw error(F("Failed to initialize database: %s") % e.what());
196*11be35a1SLionel Sambuc     }
197*11be35a1SLionel Sambuc }
198*11be35a1SLionel Sambuc 
199*11be35a1SLionel Sambuc 
200*11be35a1SLionel Sambuc /// Backs up a database for schema migration purposes.
201*11be35a1SLionel Sambuc ///
202*11be35a1SLionel Sambuc /// \todo We should probably use the SQLite backup API instead of doing a raw
203*11be35a1SLionel Sambuc /// file copy.  We issue our backup call with the database already open, but
204*11be35a1SLionel Sambuc /// because it is quiescent, it's OK to do so.
205*11be35a1SLionel Sambuc ///
206*11be35a1SLionel Sambuc /// \param source Location of the database to be backed up.
207*11be35a1SLionel Sambuc /// \param old_version Version of the database's CURRENT schema, used to
208*11be35a1SLionel Sambuc ///     determine the name of the backup file.
209*11be35a1SLionel Sambuc ///
210*11be35a1SLionel Sambuc /// \throw error If there is a problem during the backup.
211*11be35a1SLionel Sambuc void
backup_database(const fs::path & source,const int old_version)212*11be35a1SLionel Sambuc store::detail::backup_database(const fs::path& source, const int old_version)
213*11be35a1SLionel Sambuc {
214*11be35a1SLionel Sambuc     const fs::path target(F("%s.v%s.backup") % source.str() % old_version);
215*11be35a1SLionel Sambuc 
216*11be35a1SLionel Sambuc     LI(F("Backing up database %s to %s") % source % target);
217*11be35a1SLionel Sambuc 
218*11be35a1SLionel Sambuc     std::ifstream input(source.c_str());
219*11be35a1SLionel Sambuc     if (!input)
220*11be35a1SLionel Sambuc         throw error(F("Cannot open database file %s") % source);
221*11be35a1SLionel Sambuc 
222*11be35a1SLionel Sambuc     std::ofstream output(target.c_str());
223*11be35a1SLionel Sambuc     if (!output)
224*11be35a1SLionel Sambuc         throw error(F("Cannot create database backup file %s") % target);
225*11be35a1SLionel Sambuc 
226*11be35a1SLionel Sambuc     char buffer[1024];
227*11be35a1SLionel Sambuc     while (input.good()) {
228*11be35a1SLionel Sambuc         input.read(buffer, sizeof(buffer));
229*11be35a1SLionel Sambuc         if (input.good() || input.eof())
230*11be35a1SLionel Sambuc             output.write(buffer, input.gcount());
231*11be35a1SLionel Sambuc     }
232*11be35a1SLionel Sambuc     if (!input.good() && !input.eof())
233*11be35a1SLionel Sambuc         throw error(F("Error while reading input file %s") % source);
234*11be35a1SLionel Sambuc }
235*11be35a1SLionel Sambuc 
236*11be35a1SLionel Sambuc 
237*11be35a1SLionel Sambuc /// Internal implementation for the backend.
238*11be35a1SLionel Sambuc struct store::backend::impl {
239*11be35a1SLionel Sambuc     /// The SQLite database this backend talks to.
240*11be35a1SLionel Sambuc     sqlite::database database;
241*11be35a1SLionel Sambuc 
242*11be35a1SLionel Sambuc     /// Constructor.
243*11be35a1SLionel Sambuc     ///
244*11be35a1SLionel Sambuc     /// \param database_ The SQLite database instance.
245*11be35a1SLionel Sambuc     /// \param metadata_ The metadata for the loaded database.  This must match
246*11be35a1SLionel Sambuc     ///     the schema version we implement in this module; otherwise, a
247*11be35a1SLionel Sambuc     ///     migration is necessary.
248*11be35a1SLionel Sambuc     ///
249*11be35a1SLionel Sambuc     /// \throw integrity_error If the schema in the database is too modern,
250*11be35a1SLionel Sambuc     ///     which might indicate some form of corruption or an old binary.
251*11be35a1SLionel Sambuc     /// \throw old_schema_error If the schema in the database is older than our
252*11be35a1SLionel Sambuc     ///     currently-implemented version and needs an upgrade.  The caller can
253*11be35a1SLionel Sambuc     ///     use migrate_schema() to fix this problem.
implstore::backend::impl254*11be35a1SLionel Sambuc     impl(sqlite::database& database_, const metadata& metadata_) :
255*11be35a1SLionel Sambuc         database(database_)
256*11be35a1SLionel Sambuc     {
257*11be35a1SLionel Sambuc         const int database_version = metadata_.schema_version();
258*11be35a1SLionel Sambuc 
259*11be35a1SLionel Sambuc         if (database_version == detail::current_schema_version) {
260*11be35a1SLionel Sambuc             // OK.
261*11be35a1SLionel Sambuc         } else if (database_version < detail::current_schema_version) {
262*11be35a1SLionel Sambuc             throw old_schema_error(database_version);
263*11be35a1SLionel Sambuc         } else if (database_version > detail::current_schema_version) {
264*11be35a1SLionel Sambuc             throw integrity_error(
265*11be35a1SLionel Sambuc                 F("Database at schema version %s, which is newer than the "
266*11be35a1SLionel Sambuc                   "supported version %s")
267*11be35a1SLionel Sambuc                 % database_version % detail::current_schema_version);
268*11be35a1SLionel Sambuc         }
269*11be35a1SLionel Sambuc     }
270*11be35a1SLionel Sambuc };
271*11be35a1SLionel Sambuc 
272*11be35a1SLionel Sambuc 
273*11be35a1SLionel Sambuc /// Constructs a new backend.
274*11be35a1SLionel Sambuc ///
275*11be35a1SLionel Sambuc /// \param pimpl_ The internal data.
backend(impl * pimpl_)276*11be35a1SLionel Sambuc store::backend::backend(impl* pimpl_) :
277*11be35a1SLionel Sambuc     _pimpl(pimpl_)
278*11be35a1SLionel Sambuc {
279*11be35a1SLionel Sambuc }
280*11be35a1SLionel Sambuc 
281*11be35a1SLionel Sambuc 
282*11be35a1SLionel Sambuc /// Destructor.
~backend(void)283*11be35a1SLionel Sambuc store::backend::~backend(void)
284*11be35a1SLionel Sambuc {
285*11be35a1SLionel Sambuc }
286*11be35a1SLionel Sambuc 
287*11be35a1SLionel Sambuc 
288*11be35a1SLionel Sambuc /// Opens a database in read-only mode.
289*11be35a1SLionel Sambuc ///
290*11be35a1SLionel Sambuc /// \param file The database file to be opened.
291*11be35a1SLionel Sambuc ///
292*11be35a1SLionel Sambuc /// \return The backend representation.
293*11be35a1SLionel Sambuc ///
294*11be35a1SLionel Sambuc /// \throw store::error If there is any problem opening the database.
295*11be35a1SLionel Sambuc store::backend
open_ro(const fs::path & file)296*11be35a1SLionel Sambuc store::backend::open_ro(const fs::path& file)
297*11be35a1SLionel Sambuc {
298*11be35a1SLionel Sambuc     sqlite::database db = do_open(file, sqlite::open_readonly);
299*11be35a1SLionel Sambuc     return backend(new impl(db, metadata::fetch_latest(db)));
300*11be35a1SLionel Sambuc }
301*11be35a1SLionel Sambuc 
302*11be35a1SLionel Sambuc 
303*11be35a1SLionel Sambuc /// Opens a database in read-write mode and creates it if necessary.
304*11be35a1SLionel Sambuc ///
305*11be35a1SLionel Sambuc /// \param file The database file to be opened.
306*11be35a1SLionel Sambuc ///
307*11be35a1SLionel Sambuc /// \return The backend representation.
308*11be35a1SLionel Sambuc ///
309*11be35a1SLionel Sambuc /// \throw store::error If there is any problem opening or creating
310*11be35a1SLionel Sambuc ///     the database.
311*11be35a1SLionel Sambuc store::backend
open_rw(const fs::path & file)312*11be35a1SLionel Sambuc store::backend::open_rw(const fs::path& file)
313*11be35a1SLionel Sambuc {
314*11be35a1SLionel Sambuc     sqlite::database db = do_open(file, sqlite::open_readwrite |
315*11be35a1SLionel Sambuc                                   sqlite::open_create);
316*11be35a1SLionel Sambuc     if (empty_database(db))
317*11be35a1SLionel Sambuc         return backend(new impl(db, detail::initialize(db)));
318*11be35a1SLionel Sambuc     else
319*11be35a1SLionel Sambuc         return backend(new impl(db, metadata::fetch_latest(db)));
320*11be35a1SLionel Sambuc }
321*11be35a1SLionel Sambuc 
322*11be35a1SLionel Sambuc 
323*11be35a1SLionel Sambuc /// Gets the connection to the SQLite database.
324*11be35a1SLionel Sambuc ///
325*11be35a1SLionel Sambuc /// \return A database connection.
326*11be35a1SLionel Sambuc sqlite::database&
database(void)327*11be35a1SLionel Sambuc store::backend::database(void)
328*11be35a1SLionel Sambuc {
329*11be35a1SLionel Sambuc     return _pimpl->database;
330*11be35a1SLionel Sambuc }
331*11be35a1SLionel Sambuc 
332*11be35a1SLionel Sambuc 
333*11be35a1SLionel Sambuc /// Opens a transaction.
334*11be35a1SLionel Sambuc ///
335*11be35a1SLionel Sambuc /// \return A new transaction.
336*11be35a1SLionel Sambuc store::transaction
start(void)337*11be35a1SLionel Sambuc store::backend::start(void)
338*11be35a1SLionel Sambuc {
339*11be35a1SLionel Sambuc     return transaction(*this);
340*11be35a1SLionel Sambuc }
341*11be35a1SLionel Sambuc 
342*11be35a1SLionel Sambuc 
343*11be35a1SLionel Sambuc /// Migrates the schema of a database to the current version.
344*11be35a1SLionel Sambuc ///
345*11be35a1SLionel Sambuc /// The algorithm implemented here performs a migration step for every
346*11be35a1SLionel Sambuc /// intermediate version between the schema version in the database to the
347*11be35a1SLionel Sambuc /// version implemented in this file.  This should permit upgrades from
348*11be35a1SLionel Sambuc /// arbitrary old databases.
349*11be35a1SLionel Sambuc ///
350*11be35a1SLionel Sambuc /// \param file The database whose schema to upgrade.
351*11be35a1SLionel Sambuc ///
352*11be35a1SLionel Sambuc /// \throw error If there is a problem with the migration.
353*11be35a1SLionel Sambuc void
migrate_schema(const utils::fs::path & file)354*11be35a1SLionel Sambuc store::migrate_schema(const utils::fs::path& file)
355*11be35a1SLionel Sambuc {
356*11be35a1SLionel Sambuc     sqlite::database db = do_open(file, sqlite::open_readwrite);
357*11be35a1SLionel Sambuc 
358*11be35a1SLionel Sambuc     const int version_from = metadata::fetch_latest(db).schema_version();
359*11be35a1SLionel Sambuc     const int version_to = detail::current_schema_version;
360*11be35a1SLionel Sambuc     if (version_from == version_to) {
361*11be35a1SLionel Sambuc         throw error(F("Database already at schema version %s; migration not "
362*11be35a1SLionel Sambuc                       "needed") % version_from);
363*11be35a1SLionel Sambuc     } else if (version_from > version_to) {
364*11be35a1SLionel Sambuc         throw error(F("Database at schema version %s, which is newer than the "
365*11be35a1SLionel Sambuc                       "supported version %s") % version_from % version_to);
366*11be35a1SLionel Sambuc     }
367*11be35a1SLionel Sambuc 
368*11be35a1SLionel Sambuc     detail::backup_database(file, version_from);
369*11be35a1SLionel Sambuc 
370*11be35a1SLionel Sambuc     for (int i = version_from; i < version_to; ++i) {
371*11be35a1SLionel Sambuc         LI(F("Migrating schema from version %s to %s") % i % (i + 1));
372*11be35a1SLionel Sambuc         migrate_schema_step(db, i, i + 1);
373*11be35a1SLionel Sambuc     }
374*11be35a1SLionel Sambuc }
375