#
#
# patch "ChangeLog"
# from [70a93f32459f008e2055e300dd0a3b98793fdebc]
# to [c987ea8784a09997591ff5625b42c39886dace67]
#
# patch "database.cc"
# from [c0b7bcc73596dbe49ceb18c98141c360b0cabf64]
# to [e72a2c3e9de3c9d3e1ece2ecd9f6939f18ee8ed7]
#
# patch "database.hh"
# from [ff098459a66c8681188f7d66c1be55cf0ac109d1]
# to [9713f6e00494fbf2ca1a4b3bdff0d3cbe9b7a545]
#
# patch "schema_migration.cc"
# from [d55940e2585c1813bfa193fdd6d302c0219d1eca]
# to [04a62f69aacaa7b4bbce7a3eb8759895c68b9205]
#
# patch "schema_migration.hh"
# from [d17dda6f74de9d6cc6a84c8faf222e5c0bb8013e]
# to [62bbd4fe56ec69ab000f76a88f3cf4219cc88034]
#
============================================================
--- ChangeLog 70a93f32459f008e2055e300dd0a3b98793fdebc
+++ ChangeLog c987ea8784a09997591ff5625b42c39886dace67
@@ -1,3 +1,14 @@
+2007-01-20 Zack Weinberg
+
+ * database.cc: Make database::info work on arbitrarily screwed up
+ databases. Also make its output consistently formatted and easier
+ to read. Check for SQLite 3 database in more places.
+ * database.hh: Tweaks to database private member declarations.
+ * schema_migration.cc: Restructure diagnostics of mismatched
+ schemas so we can provide a classification in database::info.
+ * schema_migration.hh: New interface, describe_sql_schema.
+ calculate_schema_id is no longer visible.
+
2007-01-14 Nathaniel Smith
* m4/boost.m4: Check for another possible library suffix used on
============================================================
--- database.cc c0b7bcc73596dbe49ceb18c98141c360b0cabf64
+++ database.cc e72a2c3e9de3c9d3e1ece2ecd9f6939f18ee8ed7
@@ -447,6 +447,7 @@ database::dump(ostream & out)
// don't care about schema checking etc.
check_filename();
check_db_exists();
+ check_sqlite_format_version(filename);
open();
{
transaction_guard guard(*this);
@@ -524,107 +525,174 @@ database::debug(string const & sql, ostr
}
}
+// Subroutine of info(). This compares strings that might either be numbers
+// or error messages surrounded by square brackets. We want the longest
+// number, even if there's an error message that's longer than that.
+static bool longest_number(string a, string b)
+{
+ if(a.length() > 0 && a[0] == '[')
+ return true; // b is longer
+ if(b.length() > 0 && b[0] == '[')
+ return false; // a is longer
-namespace
+ return a.length() < b.length();
+}
+
+// Subroutine of info() and some things it calls.
+// Given an informative_failure which is believed to represent an SQLite
+// error, either return a string version of the error message (if it was an
+// SQLite error) or rethrow the execption (if it wasn't).
+static string
+format_sqlite_error_for_info(informative_failure const & e)
{
- unsigned long
- add(unsigned long count, unsigned long & total)
- {
- total += count;
- return count;
- }
+ string err(e.what());
+ string prefix = _("error: ");
+ prefix.append("sqlite error: ");
+ if (err.find(prefix) != 0)
+ throw;
+
+ err.replace(0, prefix.length(), "[");
+ string::size_type nl = err.find('\n');
+ if (nl != string::npos)
+ err.erase(nl);
+
+ err.append("]");
+ return err;
}
+
void
database::info(ostream & out)
{
- string id;
- calculate_schema_id(sql(), id);
+ // don't check the schema
+ check_filename();
+ check_db_exists();
+ check_sqlite_format_version(filename);
+ open();
+
+ vector counts;
+ counts.push_back(count("rosters"));
+ counts.push_back(count("roster_deltas"));
+ counts.push_back(count("files"));
+ counts.push_back(count("file_deltas"));
+ counts.push_back(count("revisions"));
+ counts.push_back(count("revision_ancestry"));
+ counts.push_back(count("revision_certs"));
- unsigned long total = 0UL;
-
- u64 num_nodes;
{
results res;
- fetch(res, one_col, any_rows, query("SELECT node FROM next_roster_node_number"));
- if (res.empty())
- num_nodes = 0;
- else
+ try
{
- I(res.size() == 1);
- num_nodes = lexical_cast(res[0][0]) - 1;
+ fetch(res, one_col, any_rows,
+ query("SELECT node FROM next_roster_node_number"));
+ if (res.empty())
+ counts.push_back("0");
+ else
+ {
+ I(res.size() == 1);
+ counts.push_back((F("%u")
+ % (lexical_cast(res[0][0]) - 1)).str());
+ }
}
+ catch (informative_failure const & e)
+ {
+ counts.push_back(format_sqlite_error_for_info(e));
+ }
}
-#define SPACE_USAGE(TABLE, COLS) add(space_usage(TABLE, COLS), total)
+ vector bytes;
+ {
+ u64 total = 0;
+ bytes.push_back(space("rosters",
+ "length(id) + length(checksum) + length(data)",
+ total));
+ bytes.push_back(space("roster_deltas",
+ "length(id) + length(checksum)"
+ "+ length(base) + length(delta)", total));
+ bytes.push_back(space("files", "length(id) + length(data)", total));
+ bytes.push_back(space("file_deltas",
+ "length(id) + length(base) + length(delta)", total));
+ bytes.push_back(space("revisions", "length(id) + length(data)", total));
+ bytes.push_back(space("revision_ancestry",
+ "length(parent) + length(child)", total));
+ bytes.push_back(space("revision_certs",
+ "length(hash) + length(id) + length(name)"
+ "+ length(value) + length(keypair)"
+ "+ length(signature)", total));
+ bytes.push_back(space("heights", "length(revision) + length(height)",
+ total));
+ bytes.push_back((F("%u") % total).str());
+ }
- out << ( \
+ // pad each vector's strings on the left with spaces to make them all the
+ // same length
+ {
+ string::size_type width
+ = max_element(counts.begin(), counts.end(), longest_number)->length();
+ for(vector::iterator i = counts.begin(); i != counts.end(); i++)
+ if (width > i->length() && (*i)[0] != '[')
+ i->insert(0, width - i->length(), ' ');
+
+ width = max_element(bytes.begin(), bytes.end(), longest_number)->length();
+ for(vector::iterator i = bytes.begin(); i != bytes.end(); i++)
+ if (width > i->length() && (*i)[0] != '[')
+ i->insert(0, width - i->length(), ' ');
+ }
+
+ i18n_format form =
F("schema version : %s\n"
"counts:\n"
- " full rosters : %u\n"
- " roster deltas : %u\n"
- " full files : %u\n"
- " file deltas : %u\n"
- " revisions : %u\n"
- " ancestry edges : %u\n"
- " certs : %u\n"
- " logical files : %u\n"
+ " full rosters : %s\n"
+ " roster deltas : %s\n"
+ " full files : %s\n"
+ " file deltas : %s\n"
+ " revisions : %s\n"
+ " ancestry edges : %s\n"
+ " certs : %s\n"
+ " logical files : %s\n"
"bytes:\n"
- " full rosters : %u\n"
- " roster deltas : %u\n"
- " full files : %u\n"
- " file deltas : %u\n"
- " revisions : %u\n"
- " cached ancestry : %u\n"
- " certs : %u\n"
- " heights : %u\n"
- " total : %u\n"
+ " full rosters : %s\n"
+ " roster deltas : %s\n"
+ " full files : %s\n"
+ " file deltas : %s\n"
+ " revisions : %s\n"
+ " cached ancestry : %s\n"
+ " certs : %s\n"
+ " heights : %s\n"
+ " total : %s\n"
"database:\n"
- " page size : %u\n"
- " cache size : %u"
- )
- % id
- // counts
- % count("rosters")
- % count("roster_deltas")
- % count("files")
- % count("file_deltas")
- % count("revisions")
- % count("revision_ancestry")
- % count("revision_certs")
- % num_nodes
- // bytes
- % SPACE_USAGE("rosters", "length(id) + length(checksum) + length(data)")
- % SPACE_USAGE("roster_deltas", "length(id) + length(checksum) + length(base) + length(delta)")
- % SPACE_USAGE("files", "length(id) + length(data)")
- % SPACE_USAGE("file_deltas", "length(id) + length(base) + length(delta)")
- % SPACE_USAGE("revisions", "length(id) + length(data)")
- % SPACE_USAGE("revision_ancestry", "length(parent) + length(child)")
- % SPACE_USAGE("revision_certs", "length(hash) + length(id) + length(name)"
- " + length(value) + length(keypair) + length(signature)")
- % SPACE_USAGE("heights","length(revision) + length(height)")
- % total
- % page_size()
- % cache_size()
- ) << "\n"; // final newline is kept out of the translation
+ " page size : %s\n"
+ " cache size : %s"
+ );
-#undef SPACE_USAGE
+ form = form % describe_sql_schema(__sql);
+
+ for (vector::iterator i = counts.begin(); i != counts.end(); i++)
+ form = form % *i;
+
+ for (vector::iterator i = bytes.begin(); i != bytes.end(); i++)
+ form = form % *i;
+
+ form = form % page_size();
+ form = form % cache_size();
+
+ out << form.str() << "\n"; // final newline is kept out of the translation
+
+ close();
}
void
database::version(ostream & out)
{
- string id;
-
check_filename();
check_db_exists();
+ check_sqlite_format_version(filename);
open();
- calculate_schema_id(__sql, id);
+ out << (F("database schema version: %s") % describe_sql_schema(__sql)).str()
+ << "\n";
close();
-
- out << F("database schema version: %s") % id << endl;
}
void
@@ -632,6 +700,7 @@ database::migrate()
{
check_filename();
check_db_exists();
+ check_sqlite_format_version(filename);
open();
migrate_sql_schema(__sql, *__app);
@@ -644,6 +713,7 @@ database::test_migration_step(string con
{
check_filename();
check_db_exists();
+ check_sqlite_format_version(filename);
open();
::test_migration_step(__sql, *__app, schema);
@@ -995,24 +1065,40 @@ database::delta_exists(string const & id
return table_has_entry(ident, "id", table);
}
-unsigned long
+string
database::count(string const & table)
{
- results res;
- query q("SELECT COUNT(*) FROM " + table);
- fetch(res, one_col, one_row, q);
- return lexical_cast(res[0][0]);
+ try
+ {
+ results res;
+ query q("SELECT COUNT(*) FROM " + table);
+ fetch(res, one_col, one_row, q);
+ return (F("%u") % lexical_cast(res[0][0])).str();
+ }
+ catch (informative_failure const & e)
+ {
+ return format_sqlite_error_for_info(e);
+ }
+
}
-unsigned long
-database::space_usage(string const & table, string const & rowspace)
+string
+database::space(string const & table, string const & rowspace, u64 & total)
{
- results res;
- // COALESCE is required since SUM({empty set}) is NULL.
- // the sqlite docs for SUM suggest this as a workaround
- query q("SELECT COALESCE(SUM(" + rowspace + "), 0) FROM " + table);
- fetch(res, one_col, one_row, q);
- return lexical_cast(res[0][0]);
+ try
+ {
+ results res;
+ // SUM({empty set}) is NULL; TOTAL({empty set}) is 0.0
+ query q("SELECT TOTAL(" + rowspace + ") FROM " + table);
+ fetch(res, one_col, one_row, q);
+ u64 bytes = static_cast(lexical_cast(res[0][0]));
+ total += bytes;
+ return (F("%u") % bytes).str();
+ }
+ catch (informative_failure & e)
+ {
+ return format_sqlite_error_for_info(e);
+ }
}
unsigned int
============================================================
--- database.hh ff098459a66c8681188f7d66c1be55cf0ac109d1
+++ database.hh 9713f6e00494fbf2ca1a4b3bdff0d3cbe9b7a545
@@ -138,9 +138,10 @@ private:
// --== Generic database metadata gathering ==--
//
private:
- unsigned long count(std::string const & table);
- unsigned long space_usage(std::string const & table,
- std::string const & concatenated_columns);
+ std::string count(std::string const & table);
+ std::string space(std::string const & table,
+ std::string const & concatenated_columns,
+ u64 & total);
unsigned int page_size();
unsigned int cache_size();
============================================================
--- schema_migration.cc d55940e2585c1813bfa193fdd6d302c0219d1eca
+++ schema_migration.cc 04a62f69aacaa7b4bbce7a3eb8759895c68b9205
@@ -648,7 +648,7 @@ const size_t n_migration_events = (sizeo
const size_t n_migration_events = (sizeof migration_events
/ sizeof migration_events[0]);
-void
+static void
calculate_schema_id(sqlite3 *db, string & ident)
{
sql stmt(db, 1,
@@ -698,37 +698,96 @@ schema_to_migration(string const & id)
return 0;
}
+// This enumerates the possible mismatches between the monotone executable
+// and its database.
+enum schema_mismatch_case
+ {
+ SCHEMA_MATCHES = 0,
+ SCHEMA_MIGRATION_NEEDED,
+ SCHEMA_TOO_NEW,
+ SCHEMA_NOT_MONOTONE,
+ SCHEMA_EMPTY
+ };
+
+static schema_mismatch_case
+classify_schema(sqlite3 * db, string const & id, migration_event const * m)
+{
+ if (m)
+ {
+ if (m->migrator_sql || m->migrator_func)
+ return SCHEMA_MIGRATION_NEEDED;
+ else
+ return SCHEMA_MATCHES;
+ }
+ else
+ {
+ // Distinguish an utterly empty database, such as is created by
+ // "mtn db load < /dev/null", or by the sqlite3 command line utility
+ // if you don't give it anything to do.
+ if (id == "da39a3ee5e6b4b0d3255bfef95601890afd80709")
+ return SCHEMA_EMPTY;
+
+ // Every version of the schema has included tables named 'files',
+ // 'file_deltas', 'manifests', and 'manifest_deltas'.
+ // FIXME: Instead, use PRAGMA user_version to record an additional
+ // magic number in monotone databases.
+ int n = sql::value(db,
+ "SELECT COUNT(*) FROM sqlite_master "
+ "WHERE type = 'table' AND sql IS NOT NULL AND ("
+ " name = 'files' OR name = 'file_deltas'"
+ "OR name = 'manifests' OR name = 'manifest_deltas')");
+ if (n != 4)
+ return SCHEMA_NOT_MONOTONE;
+
+ return SCHEMA_TOO_NEW;
+ }
+}
+
+string
+describe_sql_schema(sqlite3 * db)
+{
+ I(db != NULL);
+
+ string id;
+ calculate_schema_id(db, id);
+ migration_event const *m = schema_to_migration(id);
+ schema_mismatch_case cat = classify_schema(db, id, m);
+
+ switch (cat)
+ {
+ case SCHEMA_MATCHES:
+ return (F("%s (usable)") % id).str();
+ case SCHEMA_MIGRATION_NEEDED:
+ return (F("%s (migration needed)") % id).str();
+ case SCHEMA_TOO_NEW:
+ return (F("%s (too new, cannot use)") % id).str();
+ case SCHEMA_NOT_MONOTONE:
+ return (F("%s (not a monotone database)") % id).str();
+ case SCHEMA_EMPTY:
+ return (F("%s (database has no tables!)") % id).str();
+ default:
+ I(false);
+ }
+}
+
// Provide sensible diagnostics for a database schema whose hash we do not
-// recognize.
-static void NORETURN
-diagnose_unrecognized_schema(sqlite3 * db, system_path const & filename,
+// recognize. (Shared between check_sql_schema and migrate_sql_schema.)
+static void
+diagnose_unrecognized_schema(schema_mismatch_case cat,
+ system_path const & filename,
string const & id)
{
- // Give a special message for an utterly empty sqlite3 database, such as
- // is created by "mtn db load < /dev/null", or by the sqlite3 command line
- // utility if you don't give it anything to do.
- N(id != "da39a3ee5e6b4b0d3255bfef95601890afd80709",
+ N(cat != SCHEMA_EMPTY,
F("cannot use the empty sqlite database %s\n"
"(monotone databases must be created with '%s db init')")
% filename % ui.prog_name);
- // Do a sanity check to make sure we are actually looking at a monotone
- // database, not some other sqlite3 database. Every version of the schema
- // has included tables named 'files', 'file_deltas', 'manifests', and
- // 'manifest_deltas'.
- // ??? Use PRAGMA user_version to record an additional magic number in
- // monotone databases.
- int n = sql::value(db,
- "SELECT COUNT(*) FROM sqlite_master "
- "WHERE type = 'table' AND sql IS NOT NULL "
- "AND (name = 'files' OR name = 'file_deltas'"
- " OR name = 'manifests' OR name = 'manifest_deltas')");
- N(n == 4,
+ N(cat != SCHEMA_NOT_MONOTONE,
F("%s does not appear to be a monotone database\n"
"(schema %s, core tables missing)")
% filename % id);
- N(false,
+ N(cat != SCHEMA_TOO_NEW,
F("%s appears to be a monotone database, but this version of\n"
"monotone does not recognize its schema (%s).\n"
"you probably need a newer version of monotone.")
@@ -745,13 +804,12 @@ check_sql_schema(sqlite3 * db, system_pa
string id;
calculate_schema_id(db, id);
-
migration_event const *m = schema_to_migration(id);
+ schema_mismatch_case cat = classify_schema(db, id, m);
- if (m == 0)
- diagnose_unrecognized_schema(db, filename, id);
+ diagnose_unrecognized_schema(cat, filename, id);
- N(m->migrator_sql == 0 && m->migrator_func == 0,
+ N(cat != SCHEMA_MIGRATION_NEEDED,
F("database %s is laid out according to an old schema, %s\n"
"try '%s db migrate' to upgrade\n"
"(this is irreversible; you may want to make a backup copy first)")
@@ -782,15 +840,15 @@ migrate_sql_schema(sqlite3 * db, app_sta
P(F("calculating migration for schema %s") % init);
migration_event const *m = schema_to_migration(init);
+ schema_mismatch_case cat = classify_schema(db, init, m);
- if (m == 0)
- diagnose_unrecognized_schema(db, app.db.get_filename(), init);
+ diagnose_unrecognized_schema(cat, app.db.get_filename(), init);
// We really want 'db migrate' on an up-to-date schema to be a no-op
// (no vacuum or anything, even), so that automated scripts can fire
// one off optimistically and not have to worry about getting their
// administrators to do it by hand.
- if (m->migrator_func == 0 && m->migrator_sql == 0)
+ if (cat == SCHEMA_MATCHES)
{
P(F("no migration performed; database schema already up-to-date"));
return;
============================================================
--- schema_migration.hh d17dda6f74de9d6cc6a84c8faf222e5c0bb8013e
+++ schema_migration.hh 62bbd4fe56ec69ab000f76a88f3cf4219cc88034
@@ -23,9 +23,10 @@ class system_path;
class app_state;
class system_path;
-void calculate_schema_id(sqlite3 * db, std::string & id);
void migrate_sql_schema(sqlite3 * db, app_state & app);
void check_sql_schema(sqlite3 * db, system_path const & filename);
+std::string describe_sql_schema(sqlite3 * db);
+
void test_migration_step(sqlite3 * db, app_state & app,
std::string const & schema);