# # add_file "tests/t_merge_binary.at" # # patch "ChangeLog" # from [61257f105281c1958639e4711726f487df5f13e0] # to [37ece97991f08515c18cf1d12a9330b1a5acefd3] # # patch "Makefile.am" # from [c580427f04e6c7e40ed14b6a239f725f21c84c02] # to [a6c031cdb289e4294a118c97105a04e75fdebcb0] # # patch "database.cc" # from [f169cc616adacd9e668b8e89ee40f26c49f46d6d] # to [8ba130dca3d9ea3af77ff4ca881859111ffdabb3] # # 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 @@ -5278,6 +5278,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); } @@ -391,7 +365,7 @@ 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 +497,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 +512,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 +530,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 +695,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 +821,7 @@ database::begin_transaction() { if (transaction_level == 0) - execute("BEGIN"); + execute("BEGIN"); transaction_level++; } @@ -860,9 +847,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 +859,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 +870,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 +881,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 +890,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 +899,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 +914,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 +938,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 +960,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 +976,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 +1093,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 +1115,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 +1206,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 +1414,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 +1428,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 +1459,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 +1498,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 +1528,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 +1546,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. @@ -1605,22 +1588,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 +1614,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 +1637,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 +1650,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 +1663,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 +1684,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 +1696,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 +1707,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 +1721,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 +1733,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 +1751,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 +1769,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 +1792,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 +1847,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 +1863,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 +1875,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 +1890,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 +1905,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 +1921,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 +1939,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 +2066,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 +2081,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 +2095,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 +2110,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 +2158,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 +2176,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 +2188,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 +2202,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 +2214,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])); @@ -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