#
# add_file "tests/t_merge_binary.at"
#
# patch "ChangeLog"
# from [3f11efe232c0f6d25bc099e5d444e9dc7713b75f]
# to [ea2b89d5b00d53f64a81273c24c3dfbf87f1ff10]
#
# patch "Makefile.am"
# from [c580427f04e6c7e40ed14b6a239f725f21c84c02]
# to [a6c031cdb289e4294a118c97105a04e75fdebcb0]
#
# patch "database.cc"
# from [f169cc616adacd9e668b8e89ee40f26c49f46d6d]
# to [227d05b851a171bf13c1fa3899e2183d13e9678e]
#
# patch "database.hh"
# from [77ee0f199a83b2174e046d007ee563ece0dc81d0]
# to [5689e4a21edef9cc051cade4cc56518d492dfffd]
#
# patch "schema_migration.cc"
# from [f9205058568c4e8b4b496d8b655a870f05edf0be]
# to [06a7e2c801bb9d6e789f3883fa781a394993250a]
#
# patch "tests/t_merge_binary.at"
# from []
# to [4cdc96fa85206b0b6d7488886045f1d541a55131]
#
# patch "tests/t_update_off_branch.at"
# from [1ce8c8ecbb87f94254f0c7090d738273f4812b44]
# to [00ba1adec31e5d98436ef58ea31579df603396a9]
#
--- ChangeLog
+++ ChangeLog
@@ -236,6 +236,242 @@
* win32/monotone.iss: Bump version number.
+2005-07-13 Derek Scherger
+
+ * database.cc (debug): delete stale comment
+ (delete_branch_named):
+ (delete_tag_named):
+ (clear): replace vprintf stuff with query parameters
+
+2005-07-13 Nathaniel Smith
+
+ * monotone.texi (Database): Document kill_tag_locally.
+
+2005-07-13 Nathaniel Smith
+
+ * tests/t_kill_tag_locally.at, tests/t_ambiguous_tags.at: New
+ tests.
+ * testsuite.at: Add them.
+
+2005-07-11 graydon hoare
+
+ * AUTHORS: Add Jordan.
+ * commands.cc (ls_tags): Do not uniquify tags.
+ * constants.{cc,hh} (cvs_window): Change to time_t, tighten to 5 minutes.
+ * rcs_import.cc (window): Remove.
+ (note_type): Remove dead code.
+ (is_sbr): Add test for synthetic branch roots.
+ (cvs_commit::is_synthetic_branch_root): New test.
+ (process_branch): Skip synthetic branch roots, push new branch
+ before picking branch to mark, rather than after.
+ (cvs_history::index_branchpoint_symbols): Handle vendor branches.
+ (cvs_history::push_branch): Do not duplicate root on private branches.
+ (import_branch): Fix up cluster inference.
+ (cluster_consumer::consume_cluster): New invariant.
+ * tests/t_cvsimport_drepper2.at: Modify to reflect fixes.
+
+2005-07-11 Jordan Breeding
+
+ * commands.cc (db): New subcommand "kill_tag_locally"
+ * database.{cc,hh} (delete_tag_named): New function.
+
+2005-07-12 Nathaniel Smith
+
+ * schema_migration.cc (migrator::migrate): When there is nothing
+ to be done, do nothing.
+
+2005-07-12 Nathaniel Smith
+
+ * netsync.cc (rebuild_merkle_trees): Reduce memory usage a bit,
+ and don't insert branch certs that the other side will just end up
+ throwing away (reduces network traffic).
+
+2005-07-12 Nathaniel Smith
+
+ * testsuite.at (NETSYNC_SERVE_START, NETSYNC_SERVE_N_START):
+ Really, really really fix up quoting. Really.
+ I hope.
+
+2005-07-12 Nathaniel Smith
+
+ * contrib/ciabot_monotone.py (config.project_for_branch): Clarify
+ comment text for non-Python programmers.
+
+2005-07-12 Nathaniel Smith
+
+ * testsuite.at (NETSYNC_SERVE_START, NETSYNC_SERVE_N_START): Fixup
+ quoting.
+
+2005-07-11 Nathaniel Smith
+
+ * crypto_tests.cc: New SHA1 correctness tests from Kaushik Veeraraghavan.
+ * unit_tests.cc (init_unit_test_suite):
+ * unit_tests.hh (add_crypto_tests):
+ * Makefile.am (unit_tests_SOURCES): Call them.
+ * AUTHORS: Add Kaushik Veeraraghavan.
+
+2005-07-11 Nathaniel Smith
+
+ * tests/t_netsync_exclude_default.at: New test.
+ * testsuite.at: Add it.
+ (NETSYNC_SERVE_N_START, NETSYNC_SERVE_START): Use '*' as pattern
+ when none is passed.
+
+2005-07-11 Nathaniel Smith
+
+ * monotone.texi (Network): Tweak documentation for netsync
+ commands.
+
+2005-07-11 Nathaniel Smith
+
+ * app_state.{hh,cc} (exclude_patterns, add_exclude):
+ * options.hh (OPT_EXCLUDE):
+ * monotone.cc (coptions, cpp_main): New option --exclude.
+ * commands.cc (pull, push, sync, serve): Accept it.
+ (process_netsync_args): Implement it.
+ * tests/t_netsync_exclude.at: New test.
+ * testsuite.at: Add it.
+
+2005-07-11 Timothy Brownawell
+
+ * options.hh, app_state.{hh,cc}, monotone.cc: New command specific
+ option, "--exclude=x", puts arg into a vector app.excludes .
+ Used by the netsync commands.
+ * commands.cc (netsync commands): accept said option
+ (process_netsync_args): Handle excludes.
+ * monotone.texi: document it
+
+2005-07-11 Timothy Brownawell
+
+ * interner.hh: make slightly faster
+
+2005-07-10 Nathaniel Smith
+
+ * ChangeLog, configure.ac: Re-remove mysteriously revived
+ jibberish.
+
+2005-07-10 Nathaniel Smith
+
+ * tests/t_netsync_read_permissions.at: New test.
+ * testsuite.at: Run it.
+ * netsync.cc (set_session_key, dispatch_payload)
+ (respond_to_auth_cmd): Refactor to key HMAC earlier, so error
+ packets will get the right HMAC.
+
+2005-07-10 Richard Levitte
+
+ * Makefile.am (monotone_CPPFLAGS, unit_tests_CPPFLAGS): Re-remove
+ previously removed stuff.
+
+ * ChangeLog, configure.ac: Revert accidentally-recommitted changes.
+
+2005-07-10 Richard Levitte
+
+ * monotone.texi (Network), monotone.1: Mention the default port
+ number.
+
+2005-07-10 Matthew Gregan
+
+ * configure.ac: Check for boost >= 1.32.
+
+2005-07-09 Nathaniel Smith
+
+ * schema.sql (revision_ancestry__child, revision_certs__id,
+ revision_certs__name_value): New indexes.
+ * database.cc (dump, dump_table_cb, dump_index_cb): Include
+ indexes in dumps.
+ (database::database):
+ * schema_migration.cc (migrate_monotone_schema)
+ (migrate_client_to_add_indexes):
+ * tests/t_migrate_schema.at: Corresponding migration gunk.
+
+2005-07-09 Jordan Breeding
+
+ * Makefile.am (monotone_CPPFLAGS, unit_tests_CPPFLAGS):
+ * configure.ac (BOOST_FIX_VERSION): Restrict boost compile kluges
+ to boost 1.32.
+
+2005-07-09 Nathaniel Smith
+
+ * schema_migration.cc (calculate_schema_id): Include indexes in
+ the schema id.
+
+2005-07-09 Nathaniel Smith
+
+ * ChangeLog, configure.ac: Revert accidentally-committed changes.
+
+2005-07-09 Nathaniel Smith
+
+ * monotone.texi (Generating Keys): Make it a little clearer that
+ we aren't necessarily recommending people store their passphrase
+ in plaintext.
+
+2005-07-08 Matt Johnston
+
+ * tests/t_normalized_filenames.at: expect exit code of 1 not 3 for
+ "cat manifest" with a directory in MT/work
+ * file_io.cc, netcmd.cc, transforms.cc, vocab.hh: revert changes which
+ used swap() for strings and atomic types since strings are
+ copy-on-write.
+
+2005-07-08 Matt Johnston
+
+ * file_io.cc (ident_existing_file): new function to calculate
+ the ident of a file failing gracefully if it doesn't exist
+ or is a directory.
+ * file_io.hh (classify_manifest_paths,
+ build_restricted_manifest_map): use ident_existing_file
+ * ui.cc: cast to avoid compiler warnings
+
+2005-07-07 Nathaniel Smith
+
+ * contrib/ciabot_monotone.py (Monotone.log): Fix to work with
+ 0.20.
+
+2005-07-07 Nathaniel Smith
+
+ * Makefile.am (monotone_CPPFLAGS, unit_tests_CPPFLAGS): Add
+ -DBOOST_REGEX_V4_CHAR_REGEX_TRAITS_HPP to work around g++
+ 4.0/boost 1.32.0 lossage.
+
+2005-07-07 Vaclav Haisman
+
+ * Makefile.am: Compile fix for FreeBSD.
+
+2005-07-07 Nathaniel Smith
+
+ * netsync.cc (process_hello_cmd, process_anonymous_cmd)
+ (process_auth_cmd): Change permission checking -- always build
+ merkle tree (even when a pure sink), send permission denied and
+ abort whenever client tries to read/write a branch they don't have
+ access to.
+
+2005-07-07 Nathaniel Smith
+
+ * ChangeLog: fixup formatting.
+
+2005-07-06 Matt Johnston
+
+ * database.cc (assert_sqlite3_ok): database corruption and similar
+ problems are errors, not invariants.
+
+2005-07-06 Nathaniel Smith
+
+ * commands.cc (push, pull, sync): Fix --help description.
+
+2005-07-06 Nathaniel Smith
+
+ * options.hh (OPT_SET_DEFAULT):
+ * app_state.{hh,cc} (app_state::set_default):
+ * monotone.cc (coptions, cpp_main): New option.
+ * commands.cc (pull, push, sync): Accept it.
+ (process_netsync_args): Use it.
+ * tests/t_set_default.at, testsuite.at: New test.
+
+2005-07-07 Matthew Gregan
+
+ * win32/monotone.iss: Bump version number.
+
2005-07-05 Nathaniel Smith
* debian/rules (config.status): Use bundled sqlite.
@@ -5287,6 +5523,10 @@
* tests/t_vcheck.at: New priority "todo", tweak descriptive text.
+2005-01-23 Derek Scherger
+
+ * database.{cc,hh}: convert queries to use prepared statements
+
2005-01-22 Nathaniel Smith
* tests/t_delete_dir.at: Add more commentary.
--- Makefile.am
+++ Makefile.am
@@ -287,6 +287,7 @@
$(wildcard $(srcdir)/popt/test?.c) popt/testit.sh \
sqlite/keywordhash.h \
contrib/README \
+ contrib/monoprof.sh \
contrib/monotone-notify.pl \
contrib/monotone-import.pl \
contrib/ciabot_monotone.py \
--- database.cc
+++ database.cc
@@ -58,59 +58,10 @@
extern "C" {
// some wrappers to ease migration
- int sqlite3_exec_printf(sqlite3*,const char *sqlFormat,sqlite3_callback,
- void *,char **errmsg,...);
- int sqlite3_exec_vprintf(sqlite3*,const char *sqlFormat,sqlite3_callback,
- void *,char **errmsg,va_list ap);
- int sqlite3_get_table_vprintf(sqlite3*,const char *sqlFormat,char ***resultp,
- int *nrow,int *ncolumn,char **errmsg,va_list ap);
const char *sqlite3_value_text_s(sqlite3_value *v);
+ const char *sqlite3_column_text_s(sqlite3_stmt*, int col);
}
-int sqlite3_exec_printf(sqlite3 * db,
- char const * sqlFormat,
- sqlite3_callback cb,
- void * user_data,
- char ** errmsg,
- ...)
-{
- va_list ap;
- va_start(ap, errmsg);
- int result = sqlite3_exec_vprintf(db, sqlFormat, cb,
- user_data, errmsg, ap);
- va_end(ap);
- return result;
-}
-
-int sqlite3_exec_vprintf(sqlite3 * db,
- char const * sqlFormat,
- sqlite3_callback cb,
- void * user_data,
- char ** errmsg,
- va_list ap)
-{
- char * formatted = sqlite3_vmprintf(sqlFormat, ap);
- int result = sqlite3_exec(db, formatted, cb,
- user_data, errmsg);
- sqlite3_free(formatted);
- return result;
-}
-
-int sqlite3_get_table_vprintf(sqlite3 * db,
- char const * sqlFormat,
- char *** resultp,
- int * nrow,
- int * ncolumn,
- char ** errmsg,
- va_list ap)
-{
- char * formatted = sqlite3_vmprintf(sqlFormat, ap);
- int result = sqlite3_get_table(db, formatted, resultp,
- nrow, ncolumn, errmsg);
- sqlite3_free(formatted);
- return result;
-}
-
database::database(fs::path const & fn) :
filename(fn),
// nb. update this if you change the schema. unfortunately we are not
@@ -142,6 +93,12 @@
return (const char *)(sqlite3_value_text(v));
}
+const char *
+sqlite3_column_text_s(sqlite3_stmt *stmt, int col)
+{
+ return (const char *)(sqlite3_column_text(stmt, col));
+}
+
static void
sqlite3_unbase64_fn(sqlite3_context *f, int nargs, sqlite3_value ** args)
{
@@ -201,6 +158,18 @@
}
+static void
+assert_sqlite3_ok(sqlite3 *s)
+{
+ int errcode = sqlite3_errcode(s);
+
+ if (errcode == SQLITE_OK) return;
+
+ const char * errmsg = sqlite3_errmsg(s);
+
+ E(errcode == SQLITE_OK, F("sqlite error [%d]: %s") % errcode % errmsg);
+}
+
struct sqlite3 *
database::sql(bool init)
{
@@ -223,7 +192,10 @@
throw oops(string("could not open database: ") + filename.string() +
(": " + string(sqlite3_errmsg(__sql))));
if (init)
- execute(schema_constant);
+ {
+ sqlite3_exec(__sql, schema_constant, NULL, NULL, NULL);
+ assert_sqlite3_ok(__sql);
+ }
check_schema();
install_functions(__app);
@@ -308,8 +280,9 @@
I(string(vals[1]) == "table");
*(dump->out) << vals[2] << ";\n";
dump->table_name = string(vals[0]);
- sqlite3_exec_printf(dump->sql, "SELECT * FROM '%q'",
- dump_row_cb, data, NULL, vals[0]);
+ string query = "SELECT * FROM " + string(vals[0]);
+ sqlite3_exec(dump->sql, query.c_str(), dump_row_cb, data, NULL);
+ assert_sqlite3_ok(dump->sql);
return 0;
}
@@ -342,13 +315,13 @@
"WHERE type='table' AND sql NOT NULL "
"ORDER BY name",
dump_table_cb, &req, NULL);
- I(res == SQLITE_OK);
+ assert_sqlite3_ok(req.sql);
res = sqlite3_exec(req.sql,
"SELECT name, type, sql FROM sqlite_master "
"WHERE type='index' AND sql NOT NULL "
"ORDER BY name",
dump_index_cb, &req, NULL);
- I(res == SQLITE_OK);
+ assert_sqlite3_ok(req.sql);
out << "COMMIT;\n";
}
@@ -377,11 +350,12 @@
if (last_statement == 0)
continue;
string::size_type len = last_statement + 1 - tmp.c_str();
- execute(tmp.substr(0, len).c_str());
+ sqlite3_exec(__sql, tmp.substr(0, len).c_str(), NULL, NULL, NULL);
tmp.erase(0, len);
}
- execute(tmp.c_str());
+ sqlite3_exec(__sql, tmp.c_str(), NULL, NULL, NULL);
+ assert_sqlite3_ok(__sql);
}
@@ -389,9 +363,7 @@
database::debug(string const & sql, ostream & out)
{
results res;
- // "%s" construction prevents interpretation of %-signs in the query string
- // as formatting commands.
- fetch(res, any_cols, any_rows, "%s", sql.c_str());
+ fetch(res, any_cols, any_rows, sql.c_str());
out << "'" << sql << "' -> " << res.size() << " rows\n" << endl;
for (size_t i = 0; i < res.size(); ++i)
{
@@ -523,7 +495,7 @@
{
hexenc tmp;
key_hash_code(rsa_keypair_id(res[i][0]), base64(res[i][1]), tmp);
- execute("INSERT INTO public_keys VALUES('%q', '%q', '%q')",
+ execute("INSERT INTO public_keys VALUES(?, ?, ?)",
tmp().c_str(), res[i][0].c_str(), res[i][1].c_str());
++pubkeys;
}
@@ -538,7 +510,7 @@
{
hexenc tmp;
key_hash_code(rsa_keypair_id(res[i][0]), base64< arc4 >(res[i][1]), tmp);
- execute("INSERT INTO private_keys VALUES('%q', '%q', '%q')",
+ execute("INSERT INTO private_keys VALUES(?, ?, ?)",
tmp().c_str(), res[i][0].c_str(), res[i][1].c_str());
++privkeys;
}
@@ -556,6 +528,16 @@
database::~database()
{
+ L(F("statement cache statistics\n"));
+ L(F("prepared %d statements\n") % statement_cache.size());
+
+ for (map::const_iterator i = statement_cache.begin();
+ i != statement_cache.end(); ++i)
+ {
+ L(F("%d executions of %s\n") % i->second.count % i->first);
+ sqlite3_finalize(i->second.stmt);
+ }
+
if (__sql)
{
sqlite3_close(__sql);
@@ -711,111 +693,114 @@
void
database::execute(char const * query, ...)
{
- va_list ap;
+ results res;
+ va_list args;
+ va_start(args, query);
+ fetch(res, 0, 0, query, args);
+ va_end(args);
+}
- va_start(ap, query);
-
- // log it
- char * formatted = sqlite3_vmprintf(query, ap);
- string qq;
-
- if (strlen(formatted) > constants::db_log_line_sz)
- {
- qq.assign(formatted, constants::db_log_line_sz);
- qq.append(" ...");
- }
- else
- {
- qq = formatted;
- }
- L(F("db.execute(\"%s\")\n") % qq);
-
- // do it
- char * errmsg = NULL;
- int res = sqlite3_exec(sql(), formatted, NULL, NULL, &errmsg);
- sqlite3_free(formatted);
-
- va_end(ap);
-
- assert_sqlite3_ok(res, errmsg);
-
+void
+database::fetch(results & res,
+ int const want_cols,
+ int const want_rows,
+ char const * query, ...)
+{
+ va_list args;
+ va_start(args, query);
+ fetch(res, want_cols, want_rows, query, args);
+ va_end(args);
}
void
database::fetch(results & res,
int const want_cols,
int const want_rows,
- char const * query, ...)
+ char const * query,
+ va_list args)
{
- char ** result = NULL;
int nrow;
int ncol;
- char * errmsg = NULL;
int rescode;
- va_list ap;
res.clear();
res.resize(0);
- va_start(ap, query);
- // log it
- char * formatted = sqlite3_vmprintf(query, ap);
- string qq(formatted);
- if (qq.size() > constants::log_line_sz)
- qq = qq.substr(0, constants::log_line_sz) + string(" ...");
- L(F("db.fetch(\"%s\")\n") % qq);
- sqlite3_free(formatted);
+ map::iterator i = statement_cache.find(query);
+ if (i == statement_cache.end())
+ {
+ statement_cache.insert(make_pair(query, statement()));
+ i = statement_cache.find(query);
+ I(i != statement_cache.end());
- va_end(ap);
- va_start(ap, query);
+ const char * tail;
+ sqlite3_prepare(sql(), query, -1, &i->second.stmt, &tail);
+ assert_sqlite3_ok(sql());
+ L(F("prepared statement %s\n") % query);
- // do it
- rescode = sqlite3_get_table_vprintf(sql(), query, &result, &nrow, &ncol, &errmsg, ap);
+ // no support for multiple statements here
+ E(*tail == 0,
+ F("multiple statements in query: %s\n") % query);
+ }
- va_end(ap);
+ ncol = sqlite3_column_count(i->second.stmt);
- cleanup_ptr
- result_guard(result, &sqlite3_free_table);
+ E(want_cols == any_cols || want_cols == ncol,
+ F("wanted %d columns got %d in query: %s\n") % want_cols % ncol % query);
- string ctx = string("db query [") + string(query) + "]: ";
+ // bind parameters for this execution
- assert_sqlite3_ok(rescode, errmsg, ctx);
+ int params = sqlite3_bind_parameter_count(i->second.stmt);
- if (want_cols == 0 && ncol == 0) return;
- if (want_rows == 0 && nrow == 0) return;
- if (want_cols == any_rows && ncol == 0) return;
- if (want_rows == any_rows && nrow == 0) return;
+ L(F("binding %d parameters for %s\n") % params % query);
- if (want_cols != any_cols &&
- ncol != want_cols)
- throw oops((F("%s wanted %d columns, got %s")
- % ctx % want_cols % ncol).str());
+ for (int param = 1; param <= params; param++)
+ {
+ char *value = va_arg(args, char *);
+ // nb: transient will not be good for inserts with large data blobs
+ // however, it's no worse than the previous '%q' stuff in this regard
+ // might want to wrap this logging with --debug or --verbose to limit it
- if (want_rows != any_rows &&
- nrow != want_rows)
- throw oops((F("%s wanted %d rows, got %s")
- % ctx % want_rows % nrow).str());
+ string log = string(value);
- if (!result)
- throw oops(ctx + "null result set");
+ if (log.size() > constants::log_line_sz)
+ log = log.substr(0, constants::log_line_sz);
- for (int i = 0; i < ncol; ++i)
- if (!result[i])
- throw oops(ctx + "null column name");
+ L(F("binding %d with value '%s'\n") % param % log);
- for (int row = 0; row < nrow; ++row)
+ sqlite3_bind_text(i->second.stmt, param, value, -1, SQLITE_TRANSIENT);
+ assert_sqlite3_ok(sql());
+ }
+
+ // execute and process results
+
+ nrow = 0;
+ for (rescode = sqlite3_step(i->second.stmt); rescode == SQLITE_ROW;
+ rescode = sqlite3_step(i->second.stmt))
{
- vector rowvec;
- for (int col = 0; col < ncol; ++col)
+ vector row;
+ for (int col = 0; col < ncol; col++)
{
- int i = ((1 + row) * ncol) + col;
- if (!result[i])
- throw oops(ctx + "null result value");
- else
- rowvec.push_back(result[i]);
+ const char * value = sqlite3_column_text_s(i->second.stmt, col);
+ E(value, F("null result in query: %s\n") % query);
+ row.push_back(value);
+ //L(F("row %d col %d value='%s'\n") % nrow % col % value);
}
- res.push_back(rowvec);
+ res.push_back(row);
}
+
+ if (rescode != SQLITE_DONE)
+ assert_sqlite3_ok(sql());
+
+ sqlite3_reset(i->second.stmt);
+ assert_sqlite3_ok(sql());
+
+ nrow = res.size();
+
+ i->second.count++;
+
+ E(want_rows == any_rows || want_rows == nrow,
+ F("wanted %d rows got %s in query: %s\n") % want_rows % nrow % query);
}
// general application-level logic
@@ -834,7 +819,7 @@
database::begin_transaction()
{
if (transaction_level == 0)
- execute("BEGIN");
+ execute("BEGIN");
transaction_level++;
}
@@ -860,9 +845,8 @@
string const & table)
{
results res;
- fetch(res, one_col, any_rows,
- "SELECT id FROM '%q' WHERE id = '%q'",
- table.c_str(), ident().c_str());
+ string query = "SELECT id FROM " + table + " WHERE id = ?";
+ fetch(res, one_col, any_rows, query.c_str(), ident().c_str());
I((res.size() == 1) || (res.size() == 0));
return res.size() == 1;
}
@@ -873,9 +857,8 @@
string const & table)
{
results res;
- fetch(res, one_col, any_rows,
- "SELECT id FROM '%q' WHERE id = '%q'",
- table.c_str(), ident().c_str());
+ string query = "SELECT id FROM " + table + " WHERE id = ?";
+ fetch(res, one_col, any_rows, query.c_str(), ident().c_str());
return res.size() > 0;
}
@@ -885,9 +868,9 @@
string const & table)
{
results res;
- fetch(res, one_col, any_rows,
- "SELECT id FROM '%q' WHERE id = '%q' AND base = '%q'",
- table.c_str(), ident().c_str(), base().c_str());
+ string query = "SELECT id FROM " + table + " WHERE id = ? AND base = ?";
+ fetch(res, one_col, any_rows, query.c_str(),
+ ident().c_str(), base().c_str());
I((res.size() == 1) || (res.size() == 0));
return res.size() == 1;
}
@@ -896,9 +879,8 @@
database::count(string const & table)
{
results res;
- fetch(res, one_col, one_row,
- "SELECT COUNT(*) FROM '%q'",
- table.c_str());
+ string query = "SELECT COUNT(*) FROM " + table;
+ fetch(res, one_col, one_row, query.c_str());
return lexical_cast(res[0][0]);
}
@@ -906,9 +888,8 @@
database::space_usage(string const & table, string const & concatenated_columns)
{
results res;
- fetch(res, one_col, one_row,
- "SELECT SUM(LENGTH(%s)) FROM '%q'",
- concatenated_columns.c_str(), table.c_str());
+ string query = "SELECT SUM(LENGTH(" + concatenated_columns + ")) FROM " + table;
+ fetch(res, one_col, one_row, query.c_str());
return lexical_cast(res[0][0]);
}
@@ -916,9 +897,9 @@
database::get_ids(string const & table, set< hexenc > & ids)
{
results res;
+ string query = "SELECT id FROM " + table;
+ fetch(res, one_col, any_rows, query.c_str());
- fetch(res, one_col, any_rows, "SELECT id FROM %q", table.c_str());
-
for (size_t i = 0; i < res.size(); ++i)
{
ids.insert(hexenc(res[i][0]));
@@ -931,9 +912,8 @@
string const & table)
{
results res;
- fetch(res, one_col, one_row,
- "SELECT data FROM '%q' WHERE id = '%q'",
- table.c_str(), ident().c_str());
+ string query = "SELECT data FROM " + table + " WHERE id = ?";
+ fetch(res, one_col, one_row, query.c_str(), ident().c_str());
// consistency check
base64 > rdata(res[0][0]);
@@ -956,9 +936,9 @@
I(ident() != "");
I(base() != "");
results res;
- fetch(res, one_col, one_row,
- "SELECT delta FROM '%q' WHERE id = '%q' AND base = '%q'",
- table.c_str(), ident().c_str(), base().c_str());
+ string query = "SELECT delta FROM " + table + " WHERE id = ? AND base = ?";
+ fetch(res, one_col, one_row, query.c_str(),
+ ident().c_str(), base().c_str());
base64 > del_packed = res[0][0];
unpack(del_packed, del);
@@ -978,8 +958,8 @@
base64 > dat_packed;
pack(dat, dat_packed);
- execute("INSERT INTO '%q' VALUES('%q', '%q')",
- table.c_str(), ident().c_str(), dat_packed().c_str());
+ string insert = "INSERT INTO " + table + " VALUES(?, ?)";
+ execute(insert.c_str(),ident().c_str(), dat_packed().c_str());
}
void
database::put_delta(hexenc const & ident,
@@ -994,9 +974,8 @@
base64 > del_packed;
pack(del, del_packed);
- execute("INSERT INTO '%q' VALUES('%q', '%q', '%q')",
- table.c_str(),
- ident().c_str(), base().c_str(), del_packed().c_str());
+ string insert = "INSERT INTO "+table+" VALUES(?, ?, ?)";
+ execute(insert.c_str(), ident().c_str(), base().c_str(), del_packed().c_str());
}
// static ticker cache_hits("vcache hits", "h", 1);
@@ -1112,6 +1091,8 @@
bool found_root = false;
hexenc root("");
+ string delta_query = "SELECT base FROM " + delta_table + " WHERE id = ?";
+
while (! found_root)
{
set< hexenc > next_frontier;
@@ -1132,8 +1113,10 @@
{
cycles.insert(*i);
results res;
- fetch(res, one_col, any_rows, "SELECT base from '%q' WHERE id = '%q'",
- delta_table.c_str(), (*i)().c_str());
+
+ fetch(res, one_col, any_rows,
+ delta_query.c_str(), (*i)().c_str());
+
for (size_t k = 0; k < res.size(); ++k)
{
hexenc const nxt(res[k][0]);
@@ -1221,9 +1204,8 @@
database::drop(hexenc const & ident,
string const & table)
{
- execute("DELETE FROM '%q' WHERE id = '%q'",
- table.c_str(),
- ident().c_str());
+ string drop = "DELETE FROM " + table + " WHERE id = ?";
+ execute(drop.c_str(), ident().c_str());
}
void
@@ -1430,7 +1412,7 @@
results res;
parents.clear();
fetch(res, one_col, any_rows,
- "SELECT parent FROM revision_ancestry WHERE child = '%q'",
+ "SELECT parent FROM revision_ancestry WHERE child = ?",
id.inner()().c_str());
for (size_t i = 0; i < res.size(); ++i)
parents.insert(revision_id(res[i][0]));
@@ -1444,7 +1426,7 @@
results res;
children.clear();
fetch(res, one_col, any_rows,
- "SELECT child FROM revision_ancestry WHERE parent = '%q'",
+ "SELECT child FROM revision_ancestry WHERE parent = ?",
id.inner()().c_str());
for (size_t i = 0; i < res.size(); ++i)
children.insert(revision_id(res[i][0]));
@@ -1475,7 +1457,7 @@
I(!null_id(id));
results res;
fetch(res, one_col, one_row,
- "SELECT data FROM revisions WHERE id = '%q'",
+ "SELECT data FROM revisions WHERE id = ?",
id.inner()().c_str());
base64 > rdat_packed;
@@ -1514,14 +1496,14 @@
transaction_guard guard(*this);
- execute("INSERT INTO revisions VALUES('%q', '%q')",
+ execute("INSERT INTO revisions VALUES(?, ?)",
new_id.inner()().c_str(),
d_packed().c_str());
for (edge_map::const_iterator e = rev.edges.begin();
e != rev.edges.end(); ++e)
{
- execute("INSERT INTO revision_ancestry VALUES('%q', '%q')",
+ execute("INSERT INTO revision_ancestry VALUES(?, ?)",
edge_old_revision(e).inner()().c_str(),
new_id.inner()().c_str());
}
@@ -1544,9 +1526,9 @@
void
database::delete_existing_revs_and_certs()
{
- execute("DELETE from revisions");
- execute("DELETE from revision_ancestry");
- execute("DELETE from revision_certs");
+ execute("DELETE FROM revisions");
+ execute("DELETE FROM revision_ancestry");
+ execute("DELETE FROM revision_certs");
}
/// Deletes one revision from the local database.
@@ -1562,10 +1544,9 @@
// perform the actual SQL transactions to kill rev rid here
L(F("Killing revision %s locally\n") % rid);
- execute("DELETE from revision_certs WHERE id = '%s'",rid.inner()().c_str());
- execute("DELETE from revision_ancestry WHERE child = '%s'",
- rid.inner()().c_str());
- execute("DELETE from revisions WHERE id = '%s'",rid.inner()().c_str());
+ execute("DELETE from revision_certs WHERE id = ?",rid.inner()().c_str());
+ execute("DELETE from revision_ancestry WHERE child = ?", rid.inner()().c_str());
+ execute("DELETE from revisions WHERE id = ?",rid.inner()().c_str());
}
/// Deletes all certs referring to a particular branch.
@@ -1575,9 +1556,9 @@
base64 encoded;
encode_base64(branch, encoded);
L(F("Deleting all references to branch %s\n") % branch);
- execute("DELETE FROM revision_certs WHERE name='branch' AND value ='%s'",
+ execute("DELETE FROM revision_certs WHERE name='branch' AND value =?",
encoded().c_str());
- execute("DELETE FROM branch_epochs WHERE branch='%s'",
+ execute("DELETE FROM branch_epochs WHERE branch=?",
encoded().c_str());
}
@@ -1588,7 +1569,7 @@
base64 encoded;
encode_base64(tag, encoded);
L(F("Deleting all references to tag %s\n") % tag);
- execute("DELETE FROM revision_certs WHERE name='tag' AND value ='%s'",
+ execute("DELETE FROM revision_certs WHERE name='tag' AND value =?",
encoded().c_str());
}
@@ -1605,22 +1586,22 @@
if (pattern != "")
fetch(res, one_col, any_rows,
- "SELECT id from public_keys WHERE id GLOB '%q'",
+ "SELECT id FROM public_keys WHERE id GLOB ?",
pattern.c_str());
else
fetch(res, one_col, any_rows,
- "SELECT id from public_keys");
+ "SELECT id FROM public_keys");
for (size_t i = 0; i < res.size(); ++i)
pubkeys.push_back(res[i][0]);
if (pattern != "")
fetch(res, one_col, any_rows,
- "SELECT id from private_keys WHERE id GLOB '%q'",
+ "SELECT id FROM private_keys WHERE id GLOB ?",
pattern.c_str());
else
fetch(res, one_col, any_rows,
- "SELECT id from private_keys");
+ "SELECT id FROM private_keys");
for (size_t i = 0; i < res.size(); ++i)
privkeys.push_back(res[i][0]);
@@ -1631,7 +1612,8 @@
{
keys.clear();
results res;
- fetch(res, one_col, any_rows, "SELECT id from '%q'", table.c_str());
+ string query = "SELECT id FROM " + table;
+ fetch(res, one_col, any_rows, query.c_str());
for (size_t i = 0; i < res.size(); ++i)
keys.push_back(res[i][0]);
}
@@ -1653,7 +1635,7 @@
{
results res;
fetch(res, one_col, any_rows,
- "SELECT id FROM public_keys WHERE hash = '%q'",
+ "SELECT id FROM public_keys WHERE hash = ?",
hash().c_str());
I((res.size() == 1) || (res.size() == 0));
if (res.size() == 1)
@@ -1666,7 +1648,7 @@
{
results res;
fetch(res, one_col, any_rows,
- "SELECT id FROM public_keys WHERE id = '%q'",
+ "SELECT id FROM public_keys WHERE id = ?",
id().c_str());
I((res.size() == 1) || (res.size() == 0));
if (res.size() == 1)
@@ -1679,7 +1661,7 @@
{
results res;
fetch(res, one_col, any_rows,
- "SELECT id FROM private_keys WHERE id = '%q'",
+ "SELECT id FROM private_keys WHERE id = ?",
id().c_str());
I((res.size() == 1) || (res.size() == 0));
if (res.size() == 1)
@@ -1700,7 +1682,7 @@
{
results res;
fetch(res, 2, one_row,
- "SELECT id, keydata FROM public_keys where hash = '%q'",
+ "SELECT id, keydata FROM public_keys WHERE hash = ?",
hash().c_str());
id = res[0][0];
pub_encoded = res[0][1];
@@ -1712,7 +1694,7 @@
{
results res;
fetch(res, one_col, one_row,
- "SELECT keydata FROM public_keys where id = '%q'",
+ "SELECT keydata FROM public_keys WHERE id = ?",
pub_id().c_str());
pub_encoded = res[0][0];
}
@@ -1723,7 +1705,7 @@
{
results res;
fetch(res, one_col, one_col,
- "SELECT keydata FROM private_keys where id = '%q'",
+ "SELECT keydata FROM private_keys WHERE id = ?",
priv_id().c_str());
priv_encoded = res[0][0];
}
@@ -1737,7 +1719,7 @@
I(!public_key_exists(thash));
E(!public_key_exists(pub_id),
F("another key with name '%s' already exists") % pub_id);
- execute("INSERT INTO public_keys VALUES('%q', '%q', '%q')",
+ execute("INSERT INTO public_keys VALUES(?, ?, ?)",
thash().c_str(), pub_id().c_str(), pub_encoded().c_str());
}
@@ -1749,7 +1731,7 @@
key_hash_code(priv_id, priv_encoded, thash);
E(!private_key_exists(priv_id),
F("another key with name '%s' already exists") % priv_id);
- execute("INSERT INTO private_keys VALUES('%q', '%q', '%q')",
+ execute("INSERT INTO private_keys VALUES(?, ?, ?)",
thash().c_str(), priv_id().c_str(), priv_encoded().c_str());
}
@@ -1767,14 +1749,14 @@
void
database::delete_private_key(rsa_keypair_id const & pub_id)
{
- execute("DELETE FROM private_keys WHERE id = '%q'",
+ execute("DELETE FROM private_keys WHERE id = ?",
pub_id().c_str());
}
void
database::delete_public_key(rsa_keypair_id const & pub_id)
{
- execute("DELETE FROM public_keys WHERE id = '%q'",
+ execute("DELETE FROM public_keys WHERE id = ?",
pub_id().c_str());
}
@@ -1785,11 +1767,14 @@
string const & table)
{
results res;
- fetch(res, 1, any_rows,
- "SELECT id FROM '%q' WHERE id = '%q' "
- "AND name = '%q' AND value = '%q' "
- "AND keypair = '%q' AND signature = '%q' ",
- table.c_str(),
+ string query =
+ "SELECT id FROM " + table + " WHERE id = ? "
+ "AND name = ? "
+ "AND value = ? "
+ "AND keypair = ? "
+ "AND signature = ?";
+
+ fetch(res, 1, any_rows, query.c_str(),
t.ident().c_str(),
t.name().c_str(),
t.value().c_str(),
@@ -1805,8 +1790,10 @@
{
hexenc thash;
cert_hash_code(t, thash);
- execute("INSERT INTO '%q' VALUES('%q', '%q', '%q', '%q', '%q', '%q')",
- table.c_str(),
+
+ string insert = "INSERT INTO " + table + " VALUES(?, ?, ?, ?, ?, ?)";
+
+ execute(insert.c_str(),
thash().c_str(),
t.ident().c_str(),
t.name().c_str(),
@@ -1858,9 +1845,11 @@
results res;
fetch(res, one_col, any_rows,
"SELECT name FROM sqlite_master WHERE type='view'");
+
for (size_t i = 0; i < res.size(); ++i)
{
- execute("DROP VIEW '%q'", res[i][0].c_str());
+ string drop = "DROP VIEW " + res[i][0];
+ execute(drop.c_str());
}
// register any views we're going to use
execute(views_constant);
@@ -1872,9 +1861,8 @@
string const & table)
{
results res;
- fetch(res, 5, any_rows,
- "SELECT id, name, value, keypair, signature FROM '%q' ",
- table.c_str());
+ string query = "SELECT id, name, value, keypair, signature FROM " + table;
+ fetch(res, 5, any_rows, query.c_str());
results_to_certs(res, certs);
}
@@ -1885,11 +1873,11 @@
string const & table)
{
results res;
- fetch(res, 5, any_rows,
- "SELECT id, name, value, keypair, signature FROM '%q' "
- "WHERE id = '%q'",
- table.c_str(),
- ident().c_str());
+ string query =
+ "SELECT id, name, value, keypair, signature FROM " + table +
+ " WHERE id = ?";
+
+ fetch(res, 5, any_rows, query.c_str(), ident().c_str());
results_to_certs(res, certs);
}
@@ -1900,11 +1888,10 @@
string const & table)
{
results res;
- fetch(res, 5, any_rows,
- "SELECT id, name, value, keypair, signature "
- "FROM '%q' WHERE name = '%q'",
- table.c_str(),
- name().c_str());
+ string query =
+ "SELECT id, name, value, keypair, signature FROM " + table +
+ " WHERE name = ?";
+ fetch(res, 5, any_rows, query.c_str(), name().c_str());
results_to_certs(res, certs);
}
@@ -1916,13 +1903,12 @@
string const & table)
{
results res;
- fetch(res, 5, any_rows,
- "SELECT id, name, value, keypair, signature "
- "FROM '%q' "
- "WHERE id = '%q' AND name = '%q'",
- table.c_str(),
- ident().c_str(),
- name().c_str());
+ string query =
+ "SELECT id, name, value, keypair, signature FROM " + table +
+ " WHERE id = ? AND name = ?";
+
+ fetch(res, 5, any_rows, query.c_str(),
+ ident().c_str(), name().c_str());
results_to_certs(res, certs);
}
@@ -1933,13 +1919,12 @@
string const & table)
{
results res;
- fetch(res, 5, any_rows,
- "SELECT id, name, value, keypair, signature "
- "FROM '%q' "
- "WHERE name = '%q' AND value = '%q'",
- table.c_str(),
- name().c_str(),
- val().c_str());
+ string query =
+ "SELECT id, name, value, keypair, signature FROM " + table +
+ " WHERE name = ? AND value = ?";
+
+ fetch(res, 5, any_rows, query.c_str(),
+ name().c_str(), val().c_str());
results_to_certs(res, certs);
}
@@ -1952,11 +1937,11 @@
string const & table)
{
results res;
- fetch(res, 5, any_rows,
- "SELECT id, name, value, keypair, signature "
- "FROM '%q' "
- "WHERE id = '%q' AND name = '%q' AND value = '%q'",
- table.c_str(),
+ string query =
+ "SELECT id, name, value, keypair, signature FROM " + table +
+ " WHERE id = ? AND name = ? AND value = ?";
+
+ fetch(res, 5, any_rows, query.c_str(),
ident().c_str(),
name().c_str(),
value().c_str());
@@ -2079,7 +2064,7 @@
fetch(res, 5, one_row,
"SELECT id, name, value, keypair, signature "
"FROM revision_certs "
- "WHERE hash = '%q'",
+ "WHERE hash = ?",
hash().c_str());
results_to_certs(res, certs);
I(certs.size() == 1);
@@ -2094,7 +2079,7 @@
fetch(res, one_col, any_rows,
"SELECT id "
"FROM revision_certs "
- "WHERE hash = '%q'",
+ "WHERE hash = ?",
hash().c_str());
I(res.size() == 0 || res.size() == 1);
return (res.size() == 1);
@@ -2108,7 +2093,7 @@
fetch(res, one_col, any_rows,
"SELECT id "
"FROM manifest_certs "
- "WHERE hash = '%q'",
+ "WHERE hash = ?",
hash().c_str());
I(res.size() == 0 || res.size() == 1);
return (res.size() == 1);
@@ -2123,7 +2108,7 @@
fetch(res, 5, one_row,
"SELECT id, name, value, keypair, signature "
"FROM manifest_certs "
- "WHERE hash = '%q'",
+ "WHERE hash = ?",
hash().c_str());
results_to_certs(res, certs);
I(certs.size() == 1);
@@ -2171,10 +2156,12 @@
results res;
completions.clear();
+ string pattern = partial + "*";
+
fetch(res, 1, any_rows,
- "SELECT id FROM revisions WHERE id GLOB '%q*'",
- partial.c_str());
-
+ "SELECT id FROM revisions WHERE id GLOB ?",
+ pattern.c_str());
+
for (size_t i = 0; i < res.size(); ++i)
completions.insert(revision_id(res[i][0]));
}
@@ -2187,9 +2174,11 @@
results res;
completions.clear();
+ string pattern = partial + "*";
+
fetch(res, 1, any_rows,
- "SELECT id FROM manifests WHERE id GLOB '%q*'",
- partial.c_str());
+ "SELECT id FROM manifests WHERE id GLOB ?",
+ pattern.c_str());
for (size_t i = 0; i < res.size(); ++i)
completions.insert(manifest_id(res[i][0]));
@@ -2197,8 +2186,8 @@
res.clear();
fetch(res, 1, any_rows,
- "SELECT id FROM manifest_deltas WHERE id GLOB '%q*'",
- partial.c_str());
+ "SELECT id FROM manifest_deltas WHERE id GLOB ?",
+ pattern.c_str());
for (size_t i = 0; i < res.size(); ++i)
completions.insert(manifest_id(res[i][0]));
@@ -2211,9 +2200,11 @@
results res;
completions.clear();
+ string pattern = partial + "*";
+
fetch(res, 1, any_rows,
- "SELECT id FROM files WHERE id GLOB '%q*'",
- partial.c_str());
+ "SELECT id FROM files WHERE id GLOB ?",
+ pattern.c_str());
for (size_t i = 0; i < res.size(); ++i)
completions.insert(file_id(res[i][0]));
@@ -2221,8 +2212,8 @@
res.clear();
fetch(res, 1, any_rows,
- "SELECT id FROM file_deltas WHERE id GLOB '%q*'",
- partial.c_str());
+ "SELECT id FROM file_deltas WHERE id GLOB ?",
+ pattern.c_str());
for (size_t i = 0; i < res.size(); ++i)
completions.insert(file_id(res[i][0]));
@@ -2235,9 +2226,11 @@
results res;
completions.clear();
+ string pattern = partial + "*";
+
fetch(res, 2, any_rows,
- "SELECT hash, id FROM public_keys WHERE hash GLOB '%q*'",
- partial.c_str());
+ "SELECT hash, id FROM public_keys WHERE hash GLOB ?",
+ pattern.c_str());
for (size_t i = 0; i < res.size(); ++i)
completions.insert(make_pair(key_id(res[i][0]), utf8(res[i][1])));
@@ -2245,8 +2238,8 @@
res.clear();
fetch(res, 2, any_rows,
- "SELECT hash, id FROM private_keys WHERE hash GLOB '%q*'",
- partial.c_str());
+ "SELECT hash, id FROM private_keys WHERE hash GLOB ?",
+ pattern.c_str());
for (size_t i = 0; i < res.size(); ++i)
completions.insert(make_pair(key_id(res[i][0]), utf8(res[i][1])));
@@ -2450,7 +2443,7 @@
results res;
fetch(res, 2, any_rows,
"SELECT branch, epoch FROM branch_epochs"
- " WHERE hash = '%q'",
+ " WHERE hash = ?",
eid.inner()().c_str());
I(res.size() == 1);
base64 encoded(idx(idx(res, 0), 0));
@@ -2463,7 +2456,7 @@
{
results res;
fetch(res, one_col, any_rows,
- "SELECT hash FROM branch_epochs WHERE hash = '%q'",
+ "SELECT hash FROM branch_epochs WHERE hash = ?",
eid.inner()().c_str());
I(res.size() == 1 || res.size() == 0);
return res.size() == 1;
@@ -2477,7 +2470,7 @@
encode_base64(branch, encoded);
epoch_hash_code(branch, epo, eid);
I(epo.inner()().size() == constants::epochlen);
- execute("INSERT OR REPLACE INTO branch_epochs VALUES('%q', '%q', '%q')",
+ execute("INSERT OR REPLACE INTO branch_epochs VALUES(?, ?, ?)",
eid.inner()().c_str(), encoded().c_str(), epo.inner()().c_str());
}
@@ -2486,7 +2479,7 @@
{
base64 encoded;
encode_base64(branch, encoded);
- execute("DELETE FROM branch_epochs WHERE branch = '%q'", encoded().c_str());
+ execute("DELETE FROM branch_epochs WHERE branch = ?", encoded().c_str());
}
// vars
@@ -2539,7 +2532,7 @@
encode_base64(key.second, name_encoded);
base64 value_encoded;
encode_base64(value, value_encoded);
- execute("INSERT OR REPLACE INTO db_vars VALUES('%q', '%q', '%q')",
+ execute("INSERT OR REPLACE INTO db_vars VALUES(?, ?, ?)",
key.first().c_str(),
name_encoded().c_str(),
value_encoded().c_str());
@@ -2550,7 +2543,7 @@
{
base64 name_encoded;
encode_base64(key.second, name_encoded);
- execute("DELETE FROM db_vars WHERE domain = '%q' AND name = '%q'",
+ execute("DELETE FROM db_vars WHERE domain = ? AND name = ?",
key.first().c_str(), name_encoded().c_str());
}
--- database.hh
+++ database.hh
@@ -16,6 +16,8 @@
#include
+#include
+
#include "selectors.hh"
#include "manifest.hh"
#include "numeric_vocab.hh"
@@ -72,6 +74,14 @@
std::string const schema;
void check_schema();
+ struct statement {
+ int count;
+ statement() : count(0) {}
+ sqlite3_stmt *stmt;
+ };
+
+ std::map statement_cache;
+
struct app_state * __app;
struct sqlite3 * __sql;
struct sqlite3 * sql(bool init = false);
@@ -81,12 +91,20 @@
void install_views();
typedef std::vector< std::vector > results;
+
void execute(char const * query, ...);
+
void fetch(results & res,
int const want_cols,
int const want_rows,
char const * query, ...);
-
+
+ void fetch(results & res,
+ int const want_cols,
+ int const want_rows,
+ char const * query,
+ va_list args);
+
bool exists(hexenc const & ident,
std::string const & table);
bool delta_exists(hexenc const & ident,
--- schema_migration.cc
+++ schema_migration.cc
@@ -38,9 +38,6 @@
typedef boost::tokenizer > tokenizer;
extern "C" {
-// some wrappers to ease migration
- int sqlite3_exec_printf(sqlite3*,const char *sqlFormat,sqlite3_callback,
- void *,char **errmsg,...);
const char *sqlite3_value_text_s(sqlite3_value *v);
}
@@ -156,15 +153,15 @@
{
id.clear();
string tmp, tmp2;
- int res = sqlite3_exec_printf(sql,
- "SELECT sql FROM sqlite_master "
- "WHERE type = 'table' OR type = 'index' "
- // filter out NULL sql statements, because
- // those are auto-generated indices (for
- // UNIQUE constraints, etc.).
- "AND sql IS NOT NULL "
- "ORDER BY name",
- &append_sql_stmt, &tmp, NULL);
+ int res = sqlite3_exec(sql,
+ "SELECT sql FROM sqlite_master "
+ "WHERE (type = 'table' OR type = 'index') "
+ // filter out NULL sql statements, because
+ // those are auto-generated indices (for
+ // UNIQUE constraints, etc.).
+ "AND sql IS NOT NULL "
+ "ORDER BY name",
+ &append_sql_stmt, &tmp, NULL);
if (res != SQLITE_OK)
{
sqlite3_exec(sql, "ROLLBACK", NULL, NULL, NULL);
@@ -271,21 +268,28 @@
char const * dstname,
char const * dstschema)
{
- int res =
- sqlite3_exec_printf(sql, "CREATE TABLE %s %s", NULL, NULL, errmsg,
- dstname, dstschema);
+ string create = "CREATE TABLE ";
+ create += dstname;
+ create += " ";
+ create += dstschema;
+
+ int res = sqlite3_exec(sql, create.c_str(), NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res =
- sqlite3_exec_printf(sql, "INSERT INTO %s SELECT * FROM %s",
- NULL, NULL, errmsg, dstname, srcname);
+ string insert = "INSERT INTO ";
+ insert += dstname;
+ insert += " SELECT * FROM ";
+ insert += srcname;
+
+ res = sqlite3_exec(sql, insert.c_str(), NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
-
- res =
- sqlite3_exec_printf(sql, "DROP TABLE %s",
- NULL, NULL, errmsg, srcname);
+
+ string drop = "DROP TABLE ";
+ drop += srcname;
+
+ res = sqlite3_exec(sql, drop.c_str(), NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
@@ -309,23 +313,23 @@
")"))
return false;
- int res = sqlite3_exec_printf(sql, "CREATE TABLE posting_queue "
- "("
- "url not null, -- URL we are going to send this to\n"
- "content not null -- the packets we're going to send\n"
- ")", NULL, NULL, errmsg);
+ int res = sqlite3_exec(sql, "CREATE TABLE posting_queue "
+ "("
+ "url not null, -- URL we are going to send this to\n"
+ "content not null -- the packets we're going to send\n"
+ ")", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "INSERT INTO posting_queue "
- "SELECT "
- "(url || '/' || groupname), "
- "content "
- "FROM tmp", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "INSERT INTO posting_queue "
+ "SELECT "
+ "(url || '/' || groupname), "
+ "content "
+ "FROM tmp", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
@@ -341,23 +345,23 @@
")"))
return false;
- res = sqlite3_exec_printf(sql, "CREATE TABLE incoming_queue "
- "("
- "url not null, -- URL we got this bundle from\n"
- "content not null -- the packets we're going to read\n"
- ")", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "CREATE TABLE incoming_queue "
+ "("
+ "url not null, -- URL we got this bundle from\n"
+ "content not null -- the packets we're going to read\n"
+ ")", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "INSERT INTO incoming_queue "
- "SELECT "
- "(url || '/' || groupname), "
- "content "
- "FROM tmp", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "INSERT INTO incoming_queue "
+ "SELECT "
+ "(url || '/' || groupname), "
+ "content "
+ "FROM tmp", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
@@ -376,25 +380,25 @@
))
return false;
- res = sqlite3_exec_printf(sql, "CREATE TABLE sequence_numbers "
- "("
- "url primary key, -- URL to read from\n"
- "major not null, -- 0 in news servers, may be higher in depots\n"
- "minor not null -- last article / packet sequence number we got\n"
- ")", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "CREATE TABLE sequence_numbers "
+ "("
+ "url primary key, -- URL to read from\n"
+ "major not null, -- 0 in news servers, may be higher in depots\n"
+ "minor not null -- last article / packet sequence number we got\n"
+ ")", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "INSERT INTO sequence_numbers "
- "SELECT "
- "(url || '/' || groupname), "
- "major, "
- "minor "
- "FROM tmp", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "INSERT INTO sequence_numbers "
+ "SELECT "
+ "(url || '/' || groupname), "
+ "major, "
+ "minor "
+ "FROM tmp", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
@@ -412,24 +416,24 @@
))
return false;
- res = sqlite3_exec_printf(sql, "CREATE TABLE netserver_manifests "
- "("
- "url not null, -- url of some server\n"
- "manifest not null, -- manifest which exists on url\n"
- "unique(url, manifest)"
- ")", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "CREATE TABLE netserver_manifests "
+ "("
+ "url not null, -- url of some server\n"
+ "manifest not null, -- manifest which exists on url\n"
+ "unique(url, manifest)"
+ ")", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "INSERT INTO netserver_manifests "
- "SELECT "
- "(url || '/' || groupname), "
- "manifest "
- "FROM tmp", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "INSERT INTO netserver_manifests "
+ "SELECT "
+ "(url || '/' || groupname), "
+ "manifest "
+ "FROM tmp", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
@@ -455,28 +459,28 @@
")"))
return false;
- int res = sqlite3_exec_printf(sql, "CREATE TABLE manifest_certs\n"
- "(\n"
- "hash not null unique, -- hash of remaining fields separated by \":\"\n"
- "id not null, -- joins with manifests.id or manifest_deltas.id\n"
- "name not null, -- opaque string chosen by user\n"
- "value not null, -- opaque blob\n"
- "keypair not null, -- joins with public_keys.id\n"
- "signature not null, -- RSA/SHA1 signature of \"address@hidden:val]\"\n"
- "unique(name, id, value, keypair, signature)\n"
- ")", NULL, NULL, errmsg);
+ int res = sqlite3_exec(sql, "CREATE TABLE manifest_certs\n"
+ "(\n"
+ "hash not null unique, -- hash of remaining fields separated by \":\"\n"
+ "id not null, -- joins with manifests.id or manifest_deltas.id\n"
+ "name not null, -- opaque string chosen by user\n"
+ "value not null, -- opaque blob\n"
+ "keypair not null, -- joins with public_keys.id\n"
+ "signature not null, -- RSA/SHA1 signature of \"address@hidden:val]\"\n"
+ "unique(name, id, value, keypair, signature)\n"
+ ")", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "INSERT INTO manifest_certs "
- "SELECT "
- "sha1(':', id, name, value, keypair, signature), "
- "id, name, value, keypair, signature "
- "FROM tmp", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "INSERT INTO manifest_certs "
+ "SELECT "
+ "sha1(':', id, name, value, keypair, signature), "
+ "id, name, value, keypair, signature "
+ "FROM tmp", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
@@ -494,28 +498,28 @@
")"))
return false;
- res = sqlite3_exec_printf(sql, "CREATE TABLE file_certs\n"
- "(\n"
- "hash not null unique, -- hash of remaining fields separated by \":\"\n"
- "id not null, -- joins with files.id or file_deltas.id\n"
- "name not null, -- opaque string chosen by user\n"
- "value not null, -- opaque blob\n"
- "keypair not null, -- joins with public_keys.id\n"
- "signature not null, -- RSA/SHA1 signature of \"address@hidden:val]\"\n"
- "unique(name, id, value, keypair, signature)\n"
- ")", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "CREATE TABLE file_certs\n"
+ "(\n"
+ "hash not null unique, -- hash of remaining fields separated by \":\"\n"
+ "id not null, -- joins with files.id or file_deltas.id\n"
+ "name not null, -- opaque string chosen by user\n"
+ "value not null, -- opaque blob\n"
+ "keypair not null, -- joins with public_keys.id\n"
+ "signature not null, -- RSA/SHA1 signature of \"address@hidden:val]\"\n"
+ "unique(name, id, value, keypair, signature)\n"
+ ")", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "INSERT INTO file_certs "
- "SELECT "
- "sha1(':', id, name, value, keypair, signature), "
- "id, name, value, keypair, signature "
- "FROM tmp", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "INSERT INTO file_certs "
+ "SELECT "
+ "sha1(':', id, name, value, keypair, signature), "
+ "id, name, value, keypair, signature "
+ "FROM tmp", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
@@ -529,24 +533,24 @@
")"))
return false;
- res = sqlite3_exec_printf(sql, "CREATE TABLE public_keys\n"
- "(\n"
- "hash not null unique, -- hash of remaining fields separated by \":\"\n"
- "id primary key, -- key identifier chosen by user\n"
- "keydata not null -- RSA public params\n"
- ")", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "CREATE TABLE public_keys\n"
+ "(\n"
+ "hash not null unique, -- hash of remaining fields separated by \":\"\n"
+ "id primary key, -- key identifier chosen by user\n"
+ "keydata not null -- RSA public params\n"
+ ")", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "INSERT INTO public_keys "
- "SELECT "
- "sha1(':', id, keydata), "
- "id, keydata "
- "FROM tmp", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "INSERT INTO public_keys "
+ "SELECT "
+ "sha1(':', id, keydata), "
+ "id, keydata "
+ "FROM tmp", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
@@ -560,39 +564,39 @@
")"))
return false;
- res = sqlite3_exec_printf(sql, "CREATE TABLE private_keys\n"
- "(\n"
- "hash not null unique, -- hash of remaining fields separated by \":\"\n"
- "id primary key, -- as in public_keys (same identifiers, in fact)\n"
- "keydata not null -- encrypted RSA private params\n"
- ")", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "CREATE TABLE private_keys\n"
+ "(\n"
+ "hash not null unique, -- hash of remaining fields separated by \":\"\n"
+ "id primary key, -- as in public_keys (same identifiers, in fact)\n"
+ "keydata not null -- encrypted RSA private params\n"
+ ")", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "INSERT INTO private_keys "
- "SELECT "
- "sha1(':', id, keydata), "
- "id, keydata "
- "FROM tmp", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "INSERT INTO private_keys "
+ "SELECT "
+ "sha1(':', id, keydata), "
+ "id, keydata "
+ "FROM tmp", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
// add the merkle tree stuff
- res = sqlite3_exec_printf(sql,
- "CREATE TABLE merkle_nodes\n"
- "(\n"
- "type not null, -- \"key\", \"mcert\", \"fcert\", \"manifest\"\n"
- "collection not null, -- name chosen by user\n"
- "level not null, -- tree level this prefix encodes\n"
- "prefix not null, -- label identifying node in tree\n"
- "body not null, -- binary, base64'ed node contents\n"
- "unique(type, collection, level, prefix)\n"
- ")", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql,
+ "CREATE TABLE merkle_nodes\n"
+ "(\n"
+ "type not null, -- \"key\", \"mcert\", \"fcert\", \"manifest\"\n"
+ "collection not null, -- name chosen by user\n"
+ "level not null, -- tree level this prefix encodes\n"
+ "prefix not null, -- label identifying node in tree\n"
+ "body not null, -- binary, base64'ed node contents\n"
+ "unique(type, collection, level, prefix)\n"
+ ")", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
@@ -605,74 +609,74 @@
{
int res;
- res = sqlite3_exec_printf(sql, "DROP TABLE schema_version;", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "DROP TABLE schema_version;", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "DROP TABLE posting_queue;", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "DROP TABLE posting_queue;", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "DROP TABLE incoming_queue;", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "DROP TABLE incoming_queue;", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "DROP TABLE sequence_numbers;", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "DROP TABLE sequence_numbers;", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "DROP TABLE file_certs;", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "DROP TABLE file_certs;", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "DROP TABLE netserver_manifests;", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "DROP TABLE netserver_manifests;", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "DROP TABLE merkle_nodes;", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "DROP TABLE merkle_nodes;", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql,
- "CREATE TABLE merkle_nodes\n"
- "(\n"
- "type not null, -- \"key\", \"mcert\", \"fcert\", \"rcert\"\n"
- "collection not null, -- name chosen by user\n"
- "level not null, -- tree level this prefix encodes\n"
- "prefix not null, -- label identifying node in tree\n"
- "body not null, -- binary, base64'ed node contents\n"
- "unique(type, collection, level, prefix)\n"
- ")", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql,
+ "CREATE TABLE merkle_nodes\n"
+ "(\n"
+ "type not null, -- \"key\", \"mcert\", \"fcert\", \"rcert\"\n"
+ "collection not null, -- name chosen by user\n"
+ "level not null, -- tree level this prefix encodes\n"
+ "prefix not null, -- label identifying node in tree\n"
+ "body not null, -- binary, base64'ed node contents\n"
+ "unique(type, collection, level, prefix)\n"
+ ")", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "CREATE TABLE revision_certs\n"
- "(\n"
- "hash not null unique, -- hash of remaining fields separated by \":\"\n"
- "id not null, -- joins with revisions.id\n"
- "name not null, -- opaque string chosen by user\n"
- "value not null, -- opaque blob\n"
- "keypair not null, -- joins with public_keys.id\n"
- "signature not null, -- RSA/SHA1 signature of \"address@hidden:val]\"\n"
- "unique(name, id, value, keypair, signature)\n"
- ")", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "CREATE TABLE revision_certs\n"
+ "(\n"
+ "hash not null unique, -- hash of remaining fields separated by \":\"\n"
+ "id not null, -- joins with revisions.id\n"
+ "name not null, -- opaque string chosen by user\n"
+ "value not null, -- opaque blob\n"
+ "keypair not null, -- joins with public_keys.id\n"
+ "signature not null, -- RSA/SHA1 signature of \"address@hidden:val]\"\n"
+ "unique(name, id, value, keypair, signature)\n"
+ ")", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "CREATE TABLE revisions\n"
- "(\n"
- "id primary key, -- SHA1(text of revision)\n"
- "data not null -- compressed, encoded contents of a revision\n"
- ")", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "CREATE TABLE revisions\n"
+ "(\n"
+ "id primary key, -- SHA1(text of revision)\n"
+ "data not null -- compressed, encoded contents of a revision\n"
+ ")", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql, "CREATE TABLE revision_ancestry\n"
- "(\n"
- "parent not null, -- joins with revisions.id\n"
- "child not null, -- joins with revisions.id\n"
- "unique(parent, child)\n"
- ")", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "CREATE TABLE revision_ancestry\n"
+ "(\n"
+ "parent not null, -- joins with revisions.id\n"
+ "child not null, -- joins with revisions.id\n"
+ "unique(parent, child)\n"
+ ")", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
@@ -686,19 +690,18 @@
{
int res;
- res = sqlite3_exec_printf(sql, "DROP TABLE merkle_nodes;", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql, "DROP TABLE merkle_nodes;", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql,
-
- "CREATE TABLE branch_epochs\n"
- "(\n"
- "hash not null unique, -- hash of remaining fields separated by \":\"\n"
- "branch not null unique, -- joins with revision_certs.value\n"
- "epoch not null -- random hex-encoded id\n"
- ");", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql,
+ "CREATE TABLE branch_epochs\n"
+ "(\n"
+ "hash not null unique, -- hash of remaining fields separated by \":\"\n"
+ "branch not null unique, -- joins with revision_certs.value\n"
+ "epoch not null -- random hex-encoded id\n"
+ ");", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
@@ -711,14 +714,14 @@
{
int res;
- res = sqlite3_exec_printf(sql,
- "CREATE TABLE db_vars\n"
- "(\n"
- "domain not null, -- scope of application of a var\n"
- "name not null, -- var key\n"
- "value not null, -- var value\n"
- "unique(domain, name)\n"
- ");", NULL, NULL, errmsg);
+ res = sqlite3_exec(sql,
+ "CREATE TABLE db_vars\n"
+ "(\n"
+ "domain not null, -- scope of application of a var\n"
+ "name not null, -- var key\n"
+ "value not null, -- var value\n"
+ "unique(domain, name)\n"
+ ");", NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
@@ -731,24 +734,24 @@
{
int res;
- res = sqlite3_exec_printf(sql,
- "CREATE INDEX revision_ancestry__child "
- "ON revision_ancestry (child)",
- NULL, NULL, errmsg);
+ res = sqlite3_exec(sql,
+ "CREATE INDEX revision_ancestry__child "
+ "ON revision_ancestry (child)",
+ NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql,
- "CREATE INDEX revision_certs__id "
- "ON revision_certs (id);",
- NULL, NULL, errmsg);
+ res = sqlite3_exec(sql,
+ "CREATE INDEX revision_certs__id "
+ "ON revision_certs (id);",
+ NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
- res = sqlite3_exec_printf(sql,
- "CREATE INDEX revision_certs__name_value "
- "ON revision_certs (name, value);",
- NULL, NULL, errmsg);
+ res = sqlite3_exec(sql,
+ "CREATE INDEX revision_certs__name_value "
+ "ON revision_certs (name, value);",
+ NULL, NULL, errmsg);
if (res != SQLITE_OK)
return false;
--- tests/t_merge_binary.at
+++ tests/t_merge_binary.at
@@ -0,0 +1,240 @@
+AT_SETUP([merge binary file])
+MONOTONE_SETUP
+
+NEED_UNB64
+
+# This is a real merge error. A binary file is happily merged by monotone
+# just because contains some strategically placed line feeds
+AT_XFAIL_IF(true)
+
+AT_DATA(parent.bmp.b64, [Qk1mdQAAAAAAADYAAAAoAAAAZAAAAGQAAAABABgAAAAAADB1AADrCgAA6woAAAAAAAAAAAAAQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQApC
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQApC2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsyChsy2xsy2xsy2xsy2xsy2xsy2xsy2xsyGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxsKCgoK
+])
+
+AT_DATA(left.bmp.b64, [Qk1mdQAAAAAAADYAAAAoAAAAZAAAAGQAAAABABgAAAAAADB1AADrCgAA6woAAAAAAAAAAAAAQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQApC
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQApC2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsyChsy2xsy2xsy2xsy2xsy2xsy2xsy2xsypzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3GxsKCgoK
+])
+
+AT_DATA(right.bmp.b64, [Qk1mdQAAAAAAADYAAAAoAAAAZAAAAGQAAAABABgAAAAAADB1AADrCgAA6woAAAAAAAAAAAAAOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtQApC
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtQApC2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsyChsy2xsy2xsy2xsy2xsy2xsy2xsy2xsyGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxsKCgoK
+])
+
+UNB64(parent.bmp.b64, parent.bmp)
+UNB64(left.bmp.b64, left.bmp)
+UNB64(right.bmp.b64, right.bmp)
+
+AT_CHECK(cp -f parent.bmp testfile.bmp)
+AT_CHECK(MONOTONE add testfile.bmp, [], [ignore], [ignore])
+COMMIT(testbranch)
+PARENT_SHA=`BASE_REVISION`
+
+AT_CHECK(cp -f left.bmp testfile.bmp)
+COMMIT(testbranch)
+
+REVERT_TO($PARENT_SHA)
+
+AT_CHECK(cp -f right.bmp testfile.bmp)
+COMMIT(testbranch)
+
+# merge should fail!
+AT_CHECK(MONOTONE --branch=testbranch merge, [1], [ignore], [ignore])
+
+AT_CLEANUP
--- tests/t_update_off_branch.at
+++ tests/t_update_off_branch.at