The unified diff between revisions [9b19e788..] and [8846a6b8..] is displayed below. It can also be downloaded as a raw diff.

#
#
# rename "tests/t_sql_unpack.at"
#     to "tests/t_sql_gunzip.at"
#
# patch "ChangeLog"
#  from [36ddfbc60c098baecae82e3be37bb02055100506]
#    to [cf7af27d617c006696a08efcd68a9bd9fd401a7a]
#
# patch "database.cc"
#  from [377449cef66018ee4619b66e5c08dae01129df89]
#    to [4c830b7b46f89cdc384cae08e54bfeac62335eb7]
#
# patch "schema.sql"
#  from [f44144278a4158695818d8f7e1901ac6f89e39bb]
#    to [d811d7503305093359874d46f1142861657642c0]
#
# patch "schema_migration.cc"
#  from [a13abc3a8c750aadb1ad07114c8f5b0cfc0cc67b]
#    to [c802e1820aa8381b39dde2b5fece6c29c5256efd]
#
# patch "tests/t_migrate_schema.at"
#  from [9d9a73cc20f8e367f4c828484e78c19224614b93]
#    to [e5b24dd9ccc44e36b20d1b1dc89509f0e99896fd]
#
# patch "tests/t_sql_gunzip.at"
#  from [1bb994c7c10fcfafc2a0fc9b5dd7c45b5b315174]
#    to [1e43988cec90fe9e16a51379b7b832cfc609d122]
#
# patch "testsuite.at"
#  from [7f433b14b3bd2a67eeacc49577a64e35d97ed02f]
#    to [781e96c61a849b6f628e1aa691189b64f4d1bf08]
#
============================================================
--- ChangeLog	36ddfbc60c098baecae82e3be37bb02055100506
+++ ChangeLog	cf7af27d617c006696a08efcd68a9bd9fd401a7a
@@ -517,6 +517,17 @@ 2006-01-14  Nathaniel Smith  <njs@pobox.
 	* keys.cc (require_password): Adjust accordingly.
 	* cert.cc (priv_key_exists, load_key_pair): Likewise.

+2006-01-14  Christof Petig <christof@petig-baender.de>
+
+	* database.cc, database.h: binary transparent infrastructure,
+	store and retrieve former base64 encoded columns as BLOBs
+
+	* schema_migration.cc: unbase64 changed columns, change comment
+	on files.data which indicates a different database format
+
+	* schema.sql: change comment on file which indicates, that
+	files.data is no longer base64 encoded
+
 2006-01-14  Richard Levitte  <richard@levitte.org>

 	* configure.ac: Make sure there's an empty xgettext.opts
============================================================
--- database.cc	377449cef66018ee4619b66e5c08dae01129df89
+++ database.cc	4c830b7b46f89cdc384cae08e54bfeac62335eb7
@@ -107,12 +107,6 @@ struct query
   std::string sql_cmd;
 };

-extern "C" {
-// some wrappers to ease migration
-  const char *sqlite3_value_text_s(sqlite3_value *v);
-  const char *sqlite3_column_text_s(sqlite3_stmt*, int col);
-}
-
 database::database(system_path const & fn) :
   filename(fn),
   // nb. update this if you change the schema. unfortunately we are not
@@ -120,7 +114,7 @@ database::database(system_path const & f
   // non-alphabetic ordering of tables in sql source files. we could create
   // a temporary db, write our intended schema into it, and read it back,
   // but this seems like it would be too rude. possibly revisit this issue.
-  schema("1db80c7cee8fa966913db1a463ed50bf1b0e5b0e"),
+  schema("9d2b5d7b86df00c30ac34fe87a3c20f1195bb2df"),
   __sql(NULL),
   transaction_level(0)
 {}
@@ -191,43 +185,18 @@ database::check_format()
     }
 }

-// sqlite3_value_text gives a const unsigned char * but most of the time
-// we need a signed char
-const char *
-sqlite3_value_text_s(sqlite3_value *v)
-{
-  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)
-{
-  if (nargs != 1)
-    {
-      sqlite3_result_error(f, "need exactly 1 arg to unbase64()", -1);
-      return;
-    }
-  data decoded;
-  decode_base64(base64<data>(string(sqlite3_value_text_s(args[0]))), decoded);
-  sqlite3_result_blob(f, decoded().c_str(), decoded().size(), SQLITE_TRANSIENT);
-}
-
 static void
-sqlite3_unpack_fn(sqlite3_context *f, int nargs, sqlite3_value ** args)
+sqlite3_gunzip_fn(sqlite3_context *f, int nargs, sqlite3_value ** args)
 {
   if (nargs != 1)
     {
-      sqlite3_result_error(f, "need exactly 1 arg to unpack()", -1);
+      sqlite3_result_error(f, "need exactly 1 arg to gunzip()", -1);
       return;
     }
   data unpacked;
-  unpack(base64< gzip<data> >(string(sqlite3_value_text_s(args[0]))), unpacked);
+  const char *val = (const char*) sqlite3_value_blob(args[0]);
+  int bytes = sqlite3_value_bytes(args[0]);
+  decode_gzip(gzip<data>(std::string(val,val+bytes)), unpacked);
   sqlite3_result_blob(f, unpacked().c_str(), unpacked().size(), SQLITE_TRANSIENT);
 }

@@ -349,42 +318,49 @@ dump_request
 struct
 dump_request
 {
-  dump_request() {};
+  dump_request() : sql(), out() {};
   struct sqlite3 *sql;
-  string table_name;
   ostream *out;
 };

-static int
-dump_row_cb(void *data, int n, char **vals, char **cols)
+static void
+dump_row(std::ostream &out, sqlite3_stmt *stmt, std::string const& table_name)
 {
-  dump_request *dump = reinterpret_cast<dump_request *>(data);
-  I(dump != NULL);
-  I(vals != NULL);
-  I(dump->out != NULL);
-  *(dump->out) << boost::format("INSERT INTO %s VALUES(") % dump->table_name;
-  for (int i = 0; i < n; ++i)
+  out << boost::format("INSERT INTO %s VALUES(") % table_name;
+  unsigned n = sqlite3_data_count(stmt);
+  for (unsigned i = 0; i < n; ++i)
     {
       if (i != 0)
-        *(dump->out) << ',';
+        out << ',';

-      if (vals[i] == NULL)
-        *(dump->out) << "NULL";
-      else
+      if (sqlite3_column_type(stmt, i) == SQLITE_BLOB)
         {
-          *(dump->out) << "'";
-          for (char *cp = vals[i]; *cp; ++cp)
+          out << "X'";
+          const char *val = (const char*) sqlite3_column_blob(stmt, i);
+          int bytes = sqlite3_column_bytes(stmt, i);
+          out << encode_hexenc(std::string(val,val+bytes));
+          out << "'";
+        }
+      else
+        {
+          const unsigned char *val = sqlite3_column_text(stmt, i);
+          if (val == NULL)
+            out << "NULL";
+          else
             {
-              if (*cp == '\'')
-                *(dump->out) << "''";
-              else
-                *(dump->out) << *cp;
+              out << "'";
+              for (const unsigned char *cp = val; *cp; ++cp)
+                {
+                  if (*cp == '\'')
+                    out << "''";
+                  else
+                    out << *cp;
+                }
+              out << "'";
             }
-          *(dump->out) << "'";
         }
     }
-  *(dump->out) << ");\n";
-  return 0;
+  out << ");\n";
 }

 static int
@@ -400,10 +376,24 @@ dump_table_cb(void *data, int n, char **
   I(n == 3);
   I(string(vals[1]) == "table");
   *(dump->out) << vals[2] << ";\n";
-  dump->table_name = string(vals[0]);
-  string query = "SELECT * FROM " + string(vals[0]);
-  sqlite3_exec(dump->sql, query.c_str(), dump_row_cb, data, NULL);
+  string table_name(vals[0]);
+  string query = "SELECT * FROM " + table_name;
+  sqlite3_stmt *stmt = 0;
+  sqlite3_prepare(dump->sql, query.c_str(), -1, &stmt, NULL);
   assert_sqlite3_ok(dump->sql);
+
+  int stepresult = SQLITE_DONE;
+  do
+    {
+      stepresult = sqlite3_step(stmt);
+      I(stepresult == SQLITE_DONE || stepresult == SQLITE_ROW);
+      if (stepresult == SQLITE_ROW)
+        dump_row(*(dump->out), stmt, table_name);
+    }
+  while (stepresult == SQLITE_ROW);
+
+  sqlite3_finalize(stmt);
+  assert_sqlite3_ok(dump->sql);
   return 0;
 }

@@ -547,13 +537,14 @@ database::info(ostream & out)
     % count("revision_ancestry")
     % count("revision_certs")
     // bytes
-    % SPACE_USAGE("rosters", "id || data")
-    % SPACE_USAGE("roster_deltas", "id || base || delta")
-    % SPACE_USAGE("files", "id || data")
-    % SPACE_USAGE("file_deltas", "id || base || delta")
-    % SPACE_USAGE("revisions", "id || data")
-    % SPACE_USAGE("revision_ancestry", "parent || child")
-    % SPACE_USAGE("revision_certs", "hash || id || name || value || keypair || signature")
+    % SPACE_USAGE("rosters", "length(id) + length(data)")
+    % SPACE_USAGE("roster_deltas", "length(id) + length(base) + length(delta)")
+    % SPACE_USAGE("files", "length(id) + length(data)")
+    % SPACE_USAGE("file_deltas", "length(id) + length(base) + length(delta)")
+    % SPACE_USAGE("revisions", "length(id) + length(data)")
+    % SPACE_USAGE("revision_ancestry", "length(parent) + length(child)")
+    % SPACE_USAGE("revision_certs", "length(hash) + length(id) + length(name)"
+                  " + length(value) + length(keypair) + length(signature)")
     % total;

 #undef SPACE_USAGE
@@ -712,9 +703,10 @@ database::fetch(results & res,
       vector<string> row;
       for (int col = 0; col < ncol; col++)
         {
-          const char * value = sqlite3_column_text_s(i->second.stmt(), col);
+          const char * value = (const char*)sqlite3_column_blob(i->second.stmt(), col);
+          int bytes = sqlite3_column_bytes(i->second.stmt(), col);
           E(value, F("null result in query: %s\n") % query.sql_cmd);
-          row.push_back(value);
+          row.push_back(std::string(value, value + bytes));
           //L(FL("row %d col %d value='%s'\n") % nrow % col % value);
         }
       res.push_back(row);
@@ -823,12 +815,12 @@ unsigned long
 }

 unsigned long
-database::space_usage(string const & table, string const & concatenated_columns)
+database::space_usage(string const & table, string const & rowspace)
 {
   results res;
   // COALESCE is required since SUM({empty set}) is NULL.
   // the sqlite docs for SUM suggest this as a workaround
-  query q("SELECT COALESCE(SUM(LENGTH(" + concatenated_columns + ")), 0) FROM " + table);
+  query q("SELECT COALESCE(SUM(" + rowspace + "), 0) FROM " + table);
   fetch(res, one_col, one_row, q);
   return lexical_cast<unsigned long>(res[0][0]);
 }
@@ -856,9 +848,9 @@ database::get(hexenc<id> const & ident,
   fetch(res, one_col, one_row, q % text(ident()));

   // consistency check
-  base64<gzip<data> > rdata(res[0][0]);
+  gzip<data> rdata(res[0][0]);
   data rdata_unpacked;
-  unpack(rdata, rdata_unpacked);
+  decode_gzip(rdata,rdata_unpacked);

   hexenc<id> tid;
   calculate_ident(rdata_unpacked, tid);
@@ -879,8 +871,8 @@ database::get_delta(hexenc<id> const & i
   query q("SELECT delta FROM " + table + " WHERE id = ? AND base = ?");
   fetch(res, one_col, one_row, q % text(ident()) % text(base()));

-  base64<gzip<delta> > del_packed = res[0][0];
-  unpack(del_packed, del);
+  gzip<delta> del_packed(res[0][0]);
+  decode_gzip(del_packed, del);
 }

 void
@@ -896,13 +888,13 @@ database::put(hexenc<id> const & ident,
   MM(tid);
   I(tid == ident);

-  base64<gzip<data> > dat_packed;
-  pack(dat, dat_packed);
+  gzip<data> dat_packed;
+  encode_gzip(dat, dat_packed);

   string insert = "INSERT INTO " + table + " VALUES(?, ?)";
   execute(query(insert)
           % text(ident())
-          % text(dat_packed()));
+          % blob(dat_packed()));
 }
 void
 database::put_delta(hexenc<id> const & ident,
@@ -914,14 +906,14 @@ database::put_delta(hexenc<id> const & i
   I(ident() != "");
   I(base() != "");

-  base64<gzip<delta> > del_packed;
-  pack(del, del_packed);
-
+  gzip<delta> del_packed;
+  encode_gzip(del, del_packed);
+
   string insert = "INSERT INTO "+table+" VALUES(?, ?, ?)";
   execute(query(insert)
           % text(ident())
           % text(base())
-          % text(del_packed()));
+          % blob(del_packed()));
 }

 // static ticker cache_hits("vcache hits", "h", 1);
@@ -1488,10 +1480,9 @@ database::get_revision(revision_id const
         query("SELECT data FROM revisions WHERE id = ?")
         % text(id.inner()()));

-  base64<gzip<data> > rdat_packed;
-  rdat_packed = base64<gzip<data> >(res[0][0]);
+  gzip<data> gzdata(res[0][0]);
   data rdat;
-  unpack(rdat_packed, rdat);
+  decode_gzip(gzdata,rdat);

   // verify that we got a revision with the right id
   {
@@ -1656,12 +1647,11 @@ database::put_revision(revision_id const

   // Phase 3: Write the revision data

-  base64<gzip<data> > d_packed;
-  pack(d.inner(), d_packed);
-
-  execute(query("INSERT INTO revisions VALUES(?, ?)")
+  gzip<data> d_packed;
+  encode_gzip(d.inner(), d_packed);
+  execute(query("INSERT INTO revisions VALUES(?, ?)")
           % text(new_id.inner()())
-          % text(d_packed()));
+          % blob(d_packed()));

   for (edge_map::const_iterator e = rev.edges.begin();
        e != rev.edges.end(); ++e)
@@ -1760,24 +1750,20 @@ database::delete_branch_named(cert_value
 void
 database::delete_branch_named(cert_value const & branch)
 {
-  base64<cert_value> encoded;
-  encode_base64(branch, encoded);
   L(FL("Deleting all references to branch %s\n") % branch);
   execute(query("DELETE FROM revision_certs WHERE name='branch' AND value =?")
-          % text(encoded()));
+          % blob(branch()));
   execute(query("DELETE FROM branch_epochs WHERE branch=?")
-          % text(encoded()));
+          % blob(branch()));
 }

 /// Deletes all certs referring to a particular tag.
 void
 database::delete_tag_named(cert_value const & tag)
 {
-  base64<cert_value> encoded;
-  encode_base64(tag, encoded);
   L(FL("Deleting all references to tag %s\n") % tag);
   execute(query("DELETE FROM revision_certs WHERE name='tag' AND value =?")
-          % text(encoded()));
+          % blob(tag()));
 }

 // crypto key management
@@ -1853,7 +1839,7 @@ database::get_pubkey(hexenc<id> const &
         query("SELECT id, keydata FROM public_keys WHERE hash = ?")
         % text(hash()));
   id = res[0][0];
-  pub_encoded = res[0][1];
+  encode_base64(rsa_pub_key(res[0][1]), pub_encoded);
 }

 void
@@ -1864,7 +1850,7 @@ database::get_key(rsa_keypair_id const &
   fetch(res, one_col, one_row,
         query("SELECT keydata FROM public_keys WHERE id = ?")
         % text(pub_id()));
-  pub_encoded = res[0][0];
+  encode_base64(rsa_pub_key(res[0][0]), pub_encoded);
 }

 void
@@ -1876,10 +1862,12 @@ database::put_key(rsa_keypair_id const &
   I(!public_key_exists(thash));
   E(!public_key_exists(pub_id),
     F("another key with name '%s' already exists") % pub_id);
+  rsa_pub_key pub_key;
+  decode_base64(pub_encoded, pub_key);
   execute(query("INSERT INTO public_keys VALUES(?, ?, ?)")
           % text(thash())
           % text(pub_id())
-          % text(pub_encoded()));
+          % blob(pub_key()));
 }

 void
@@ -1896,6 +1884,10 @@ database::cert_exists(cert const & t,
                       string const & table)
 {
   results res;
+  cert_value value;
+  decode_base64(t.value, value);
+  rsa_sha1_signature sig;
+  decode_base64(t.sig, sig);
   query q = query("SELECT id FROM " + table + " WHERE id = ? "
                   "AND name = ? "
                   "AND value = ? "
@@ -1903,10 +1895,10 @@ database::cert_exists(cert const & t,
                   "AND signature = ?")
     % text(t.ident())
     % text(t.name())
-    % text(t.value())
+    % blob(value())
     % text(t.key())
-    % text(t.sig());
-
+    % blob(sig());
+
   fetch(res, 1, any_rows, q);

   I(res.size() == 0 || res.size() == 1);
@@ -1919,6 +1911,10 @@ database::put_cert(cert const & t,
 {
   hexenc<id> thash;
   cert_hash_code(t, thash);
+  cert_value value;
+  decode_base64(t.value, value);
+  rsa_sha1_signature sig;
+  decode_base64(t.sig, sig);

   string insert = "INSERT INTO " + table + " VALUES(?, ?, ?, ?, ?, ?)";

@@ -1926,9 +1922,9 @@ database::put_cert(cert const & t,
           % text(thash())
           % text(t.ident())
           % text(t.name())
-          % text(t.value())
+          % blob(value())
           % text(t.key())
-          % text(t.sig()));
+          % blob(sig()));
 }

 void
@@ -1939,11 +1935,15 @@ database::results_to_certs(results const
   for (size_t i = 0; i < res.size(); ++i)
     {
       cert t;
+      base64<cert_value> value;
+      encode_base64(cert_value(res[i][2]), value);
+      base64<rsa_sha1_signature> sig;
+      encode_base64(rsa_sha1_signature(res[i][4]), sig);
       t = cert(hexenc<id>(res[i][0]),
               cert_name(res[i][1]),
-              base64<cert_value>(res[i][2]),
+              value,
               rsa_keypair_id(res[i][3]),
-              base64<rsa_sha1_signature>(res[i][4]));
+              sig);
       certs.push_back(t);
     }
 }
@@ -1952,14 +1952,10 @@ database::install_functions(app_state *
 database::install_functions(app_state * app)
 {
   // register any functions we're going to use
-  I(sqlite3_create_function(sql(), "unbase64", -1,
+  I(sqlite3_create_function(sql(), "gunzip", -1,
                            SQLITE_UTF8, NULL,
-                           &sqlite3_unbase64_fn,
+                           &sqlite3_gunzip_fn,
                            NULL, NULL) == 0);
-  I(sqlite3_create_function(sql(), "unpack", -1,
-                           SQLITE_UTF8, NULL,
-                           &sqlite3_unpack_fn,
-                           NULL, NULL) == 0);
 }

 void
@@ -2048,9 +2044,11 @@ database::get_certs(cert_name const & na
   query q("SELECT id, name, value, keypair, signature FROM " + table +
           " WHERE name = ? AND value = ?");

-  fetch(res, 5, any_rows,
+  cert_value binvalue;
+  decode_base64(val, binvalue);
+  fetch(res, 5, any_rows,
         q % text(name())
-          % text(val()));
+          % blob(binvalue()));
   results_to_certs(res, certs);
 }

@@ -2066,10 +2064,12 @@ database::get_certs(hexenc<id> const & i
   query q("SELECT id, name, value, keypair, signature FROM " + table +
           " WHERE id = ? AND name = ? AND value = ?");

+  cert_value binvalue;
+  decode_base64(value, binvalue);
   fetch(res, 5, any_rows,
         q % text(ident())
           % text(name())
-          % text(value()));
+          % blob(binvalue()));
   results_to_certs(res, certs);
 }

@@ -2386,7 +2386,7 @@ void database::complete(selector_type ty
                       spot++;
                       certvalue = i->second.substr(spot);
                       lim += "SELECT id FROM revision_certs ";
-                      lim += (boost::format("WHERE name='%s' AND unbase64(value) glob '%s'")
+                      lim += (boost::format("WHERE name='%s' AND CAST(value AS TEXT) glob '%s'")
                               % certname % certvalue).str();
                     }
                   else
@@ -2405,7 +2405,7 @@ void database::complete(selector_type ty
                       % author_cert_name
                       % tag_cert_name
                       % branch_cert_name).str();
-              lim += (boost::format(" AND unbase64(value) glob '*%s*'")
+              lim += (boost::format(" AND CAST(value AS TEXT) glob '*%s*'")
                       % i->second).str();
             }
           else if (i->first == selectors::sel_head)
@@ -2419,15 +2419,13 @@ void database::complete(selector_type ty
                 }
               else
                 {
-                  string subquery = (boost::format("SELECT DISTINCT value FROM revision_certs WHERE name='%s' and unbase64(value) glob '%s'")
+                  string subquery = (boost::format("SELECT DISTINCT value FROM revision_certs WHERE name='%s' and CAST(value AS TEXT) glob '%s'")
                                      % branch_cert_name % i->second).str();
                   results res;
                   fetch(res, one_col, any_rows, query(subquery));
                   for (size_t i = 0; i < res.size(); ++i)
                     {
-                      base64<data> row_encoded(res[i][0]);
-                      data row_decoded;
-                      decode_base64(row_encoded, row_decoded);
+                      data row_decoded(res[i][0]);
                       branch_names.push_back(row_decoded());
                     }
                 }
@@ -2466,8 +2464,7 @@ void database::complete(selector_type ty
               if ((i->first == selectors::sel_branch) && (i->second.size() == 0))
                 {
                   __app->require_workspace("the empty branch selector b: refers to the current branch");
-                  // FIXME: why do we have to glob on the unbase64(value), rather than being able to use == ?
-                  lim += (boost::format("SELECT id FROM revision_certs WHERE name='%s' AND unbase64(value) glob '%s'")
+                  lim += (boost::format("SELECT id FROM revision_certs WHERE name='%s' AND CAST(value AS TEXT) glob '%s'")
                           % branch_cert_name % __app->branch_name).str();
                   L(FL("limiting to current branch '%s'\n") % __app->branch_name);
                 }
@@ -2477,13 +2474,13 @@ void database::complete(selector_type ty
                   switch (i->first)
                     {
                     case selectors::sel_earlier:
-                      lim += (boost::format("unbase64(value) <= X'%s'") % encode_hexenc(i->second)).str();
+                      lim += (boost::format("value <= X'%s'") % encode_hexenc(i->second)).str();
                       break;
                     case selectors::sel_later:
-                      lim += (boost::format("unbase64(value) > X'%s'") % encode_hexenc(i->second)).str();
+                      lim += (boost::format("value > X'%s'") % encode_hexenc(i->second)).str();
                       break;
                     default:
-                      lim += (boost::format("unbase64(value) glob '%s%s%s'")
+                      lim += (boost::format("CAST(value AS TEXT) glob '%s%s%s'")
                               % prefix % i->second % suffix).str();
                       break;
                     }
@@ -2524,7 +2521,7 @@ void database::complete(selector_type ty
             (boost::format(" (name='%s')") % certname).str();
         }

-      query_str += (boost::format(" AND (unbase64(value) GLOB '%s%s%s')")
+      query_str += (boost::format(" AND (CAST(value AS TEXT) GLOB '%s%s%s')")
                 % prefix % partial % suffix).str();
       query_str += (boost::format(" AND (id IN %s)") % lim).str();
     }
@@ -2537,9 +2534,7 @@ void database::complete(selector_type ty
         completions.insert(res[i][0]);
       else
         {
-          base64<data> row_encoded(res[i][0]);
-          data row_decoded;
-          decode_base64(row_encoded, row_decoded);
+          data row_decoded(res[i][0]);
           completions.insert(row_decoded());
         }
     }
@@ -2555,9 +2550,7 @@ database::get_epochs(std::map<cert_value
   fetch(res, 2, any_rows, query("SELECT branch, epoch FROM branch_epochs"));
   for (results::const_iterator i = res.begin(); i != res.end(); ++i)
     {
-      base64<cert_value> encoded(idx(*i, 0));
-      cert_value decoded;
-      decode_base64(encoded, decoded);
+      cert_value decoded(idx(*i, 0));
       I(epochs.find(decoded) == epochs.end());
       epochs.insert(std::make_pair(decoded, epoch_data(idx(*i, 1))));
     }
@@ -2574,8 +2567,7 @@ database::get_epoch(epoch_id const & eid
         " WHERE hash = ?")
         % text(eid.inner()()));
   I(res.size() == 1);
-  base64<cert_value> encoded(idx(idx(res, 0), 0));
-  decode_base64(encoded, branch);
+  branch = cert_value(idx(idx(res, 0), 0));
   epo = epoch_data(idx(idx(res, 0), 1));
 }

@@ -2594,23 +2586,19 @@ database::set_epoch(cert_value const & b
 database::set_epoch(cert_value const & branch, epoch_data const & epo)
 {
   epoch_id eid;
-  base64<cert_value> encoded;
-  encode_base64(branch, encoded);
   epoch_hash_code(branch, epo, eid);
   I(epo.inner()().size() == constants::epochlen);
   execute(query("INSERT OR REPLACE INTO branch_epochs VALUES(?, ?, ?)")
           % text(eid.inner()())
-          % text(encoded())
+          % blob(branch())
           % text(epo.inner()()));
 }

 void
 database::clear_epoch(cert_value const & branch)
 {
-  base64<cert_value> encoded;
-  encode_base64(branch, encoded);
   execute(query("DELETE FROM branch_epochs WHERE branch = ?")
-          % text(encoded()));
+          % blob(branch()));
 }

 // vars
@@ -2624,12 +2612,8 @@ database::get_vars(std::map<var_key, var
   for (results::const_iterator i = res.begin(); i != res.end(); ++i)
     {
       var_domain domain(idx(*i, 0));
-      base64<var_name> name_encoded(idx(*i, 1));
-      var_name name;
-      decode_base64(name_encoded, name);
-      base64<var_value> value_encoded(idx(*i, 2));
-      var_value value;
-      decode_base64(value_encoded, value);
+      var_name name(idx(*i, 1));
+      var_value value(idx(*i, 2));
       I(vars.find(std::make_pair(domain, name)) == vars.end());
       vars.insert(std::make_pair(std::make_pair(domain, name), value));
     }
@@ -2659,24 +2643,18 @@ database::set_var(var_key const & key, v
 void
 database::set_var(var_key const & key, var_value const & value)
 {
-  base64<var_name> name_encoded;
-  encode_base64(key.second, name_encoded);
-  base64<var_value> value_encoded;
-  encode_base64(value, value_encoded);
   execute(query("INSERT OR REPLACE INTO db_vars VALUES(?, ?, ?)")
           % text(key.first())
-          % text(name_encoded())
-          % text(value_encoded()));
+          % blob(key.second())
+          % blob(value()));
 }

 void
 database::clear_var(var_key const & key)
 {
-  base64<var_name> name_encoded;
-  encode_base64(key.second, name_encoded);
   execute(query("DELETE FROM db_vars WHERE domain = ? AND name = ?")
           % text(key.first())
-          % text(name_encoded()));
+          % blob(key.second()));
 }

 // branches
@@ -2690,10 +2668,7 @@ database::get_branches(vector<string> &
     fetch(res, one_col, any_rows, q % text(cert_name));
     for (size_t i = 0; i < res.size(); ++i)
       {
-        base64<data> row_encoded(res[i][0]);
-        data name;
-        decode_base64(row_encoded, name);
-        names.push_back(name());
+        names.push_back(res[i][0]);
       }
 }

============================================================
--- schema.sql	f44144278a4158695818d8f7e1901ac6f89e39bb
+++ schema.sql	d811d7503305093359874d46f1142861657642c0
@@ -22,14 +22,14 @@ CREATE TABLE files
 CREATE TABLE files
 	(
 	id primary key,   -- strong hash of file contents
-	data not null     -- compressed, encoded contents of a file
-	);
+	data not null     -- compressed contents of a file
+	);

 CREATE TABLE file_deltas
-	(
+	(
 	id not null,      -- strong hash of file contents
 	base not null,    -- joins with files.id or file_deltas.id
-	delta not null,   -- rdiff to construct current from base
+	delta not null,   -- compressed rdiff to construct current from base
 	unique(id, base)
 	);

============================================================
--- schema_migration.cc	a13abc3a8c750aadb1ad07114c8f5b0cfc0cc67b
+++ schema_migration.cc	c802e1820aa8381b39dde2b5fece6c29c5256efd
@@ -20,6 +20,7 @@
 #include "botan/botan.h"
 #include "app_state.hh"
 #include "keys.hh"
+#include "transforms.hh"

 // this file knows how to migrate schema databases. the general strategy is
 // to hash each schema we ever use, and make a list of the SQL commands
@@ -54,12 +55,8 @@ typedef boost::tokenizer<boost::char_sep

 typedef boost::tokenizer<boost::char_separator<char> > tokenizer;

-extern "C" {
-  const char *sqlite3_value_text_s(sqlite3_value *v);
-}
-
 static string
-lowercase(string const & in)
+lowercase_facet(string const & in)
 {
   I(40==in.size());
   const int sz=40;
@@ -93,7 +90,7 @@ calculate_id(string const & in,
   Botan::Pipe p(new Botan::Hash_Filter("SHA-1"), new Botan::Hex_Encoder());
   p.process_msg(in);

-  ident = lowercase(p.read_all_as_string());
+  ident = lowercase_facet(p.read_all_as_string());
 }


@@ -118,19 +115,19 @@ sqlite_sha1_fn(sqlite3_context *f, int n

   if (nargs == 1)
     {
-      string s = (sqlite3_value_text_s(args[0]));
+      string s = reinterpret_cast<char const*>(sqlite3_value_text(args[0]));
       s.erase(remove_if(s.begin(), s.end(), is_ws()),s.end());
       tmp = s;
     }
   else
     {
-      string sep = string(sqlite3_value_text_s(args[0]));
-      string s = (sqlite3_value_text_s(args[1]));
+      string sep = string(reinterpret_cast<char const*>(sqlite3_value_text(args[0])));
+      string s = reinterpret_cast<char const*>(sqlite3_value_text(args[1]));
       s.erase(remove_if(s.begin(), s.end(), is_ws()),s.end());
       tmp = s;
       for (int i = 2; i < nargs; ++i)
         {
-          s = string(sqlite3_value_text_s(args[i]));
+          s = string(reinterpret_cast<char const*>(sqlite3_value_text(args[i])));
           s.erase(remove_if(s.begin(), s.end(), is_ws()),s.end());
           tmp += sep + s;
         }
@@ -908,6 +905,135 @@ migrate_client_to_add_rosters(sqlite3 *
   return true;
 }

+static void
+sqlite3_unbase64_fn(sqlite3_context *f, int nargs, sqlite3_value ** args)
+{
+  if (nargs != 1)
+    {
+      sqlite3_result_error(f, "need exactly 1 arg to unbase64()", -1);
+      return;
+    }
+  data decoded;
+  decode_base64(base64<data>(string(reinterpret_cast<char const*>(sqlite3_value_text(args[0])))), decoded);
+  sqlite3_result_blob(f, decoded().c_str(), decoded().size(), SQLITE_TRANSIENT);
+}
+
+// I wish I had a form of ALTER TABLE COMMENT on sqlite3
+static bool
+migrate_files_BLOB(sqlite3 * sql,
+                                    char ** errmsg,
+                                    app_state *app)
+{
+  int res;
+  I(sqlite3_create_function(sql, "unbase64", -1,
+                           SQLITE_UTF8, NULL,
+                           &sqlite3_unbase64_fn,
+                           NULL, NULL) == 0);
+  // change the encoding of file(_delta)s
+  if (!move_table(sql, errmsg,
+                  "files",
+                  "tmp",
+                  "("
+                  "id primary key,"
+                  "data not null"
+                  ")"))
+    return false;
+
+  res = logged_sqlite3_exec(sql, "CREATE TABLE files\n"
+                            "\t(\n"
+                            "\tid primary key,   -- strong hash of file contents\n"
+                            "\tdata not null     -- compressed contents of a file\n"
+                            "\t)", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+
+  res = logged_sqlite3_exec(sql, "INSERT INTO files "
+                            "SELECT id, unbase64(data) "
+                            "FROM tmp", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+
+  res = logged_sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+
+  if (!move_table(sql, errmsg,
+                  "file_deltas",
+                  "tmp",
+                  "("
+                  "id not null,"
+                  "base not null,"
+                  "delta not null"
+                  ")"))
+    return false;
+
+  res = logged_sqlite3_exec(sql, "CREATE TABLE file_deltas\n"
+                            "\t(\n"
+                            "\tid not null,      -- strong hash of file contents\n"
+                            "\tbase not null,    -- joins with files.id or file_deltas.id\n"
+                            "\tdelta not null,   -- compressed rdiff to construct current from base\n"
+                            "\tunique(id, base)\n"
+                            "\t)", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+
+  res = logged_sqlite3_exec(sql, "INSERT INTO file_deltas "
+                            "SELECT id, base, unbase64(delta) "
+                            "FROM tmp", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+
+  res = logged_sqlite3_exec(sql, "DROP TABLE tmp", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+
+  // migrate other contents which are accessed by get|put_version
+  res = logged_sqlite3_exec(sql, "UPDATE manifests SET data=unbase64(data)",
+                            NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  res = logged_sqlite3_exec(sql, "UPDATE manifest_deltas "
+                            "SET delta=unbase64(delta)", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  res = logged_sqlite3_exec(sql, "UPDATE rosters SET data=unbase64(data) ",
+                            NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  res = logged_sqlite3_exec(sql, "UPDATE roster_deltas "
+                            "SET delta=unbase64(delta)", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+
+  res = logged_sqlite3_exec(sql, "UPDATE db_vars "
+      "SET value=unbase64(value),name=unbase64(name)", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  res = logged_sqlite3_exec(sql, "UPDATE public_keys "
+      "SET keydata=unbase64(keydata)", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  res = logged_sqlite3_exec(sql, "UPDATE revision_certs "
+      "SET value=unbase64(value),signature=unbase64(signature)",
+      NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  res = logged_sqlite3_exec(sql, "UPDATE manifest_certs "
+      "SET value=unbase64(value),signature=unbase64(signature) ",
+      NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  res = logged_sqlite3_exec(sql, "UPDATE revisions "
+      "SET data=unbase64(data)", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  res = logged_sqlite3_exec(sql, "UPDATE branch_epochs "
+      "SET branch=unbase64(branch)", NULL, NULL, errmsg);
+  if (res != SQLITE_OK)
+    return false;
+  return true;
+}
+
 void
 migrate_monotone_schema(sqlite3 *sql, app_state *app)
 {
@@ -939,9 +1065,12 @@ migrate_monotone_schema(sqlite3 *sql, ap
   m.add("bd86f9a90b5d552f0be1fa9aee847ea0f317778b",
         &migrate_client_to_add_rosters);

+  m.add("1db80c7cee8fa966913db1a463ed50bf1b0e5b0e",
+        &migrate_files_BLOB);
+
   // IMPORTANT: whenever you modify this to add a new schema version, you must
   // also add a new migration test for the new schema version.  See
   // tests/t_migrate_schema.at for details.

-  m.migrate(sql, "1db80c7cee8fa966913db1a463ed50bf1b0e5b0e");
+  m.migrate(sql, "9d2b5d7b86df00c30ac34fe87a3c20f1195bb2df");
 }
============================================================
--- tests/t_migrate_schema.at	9d9a73cc20f8e367f4c828484e78c19224614b93
+++ tests/t_migrate_schema.at	e5b24dd9ccc44e36b20d1b1dc89509f0e99896fd
@@ -284,4 +284,181 @@ 5Zv/XbuXQ1m/fTpA9kr7T0e2Pv780pGhadLlP3b/
 5Zv/XbuXQ1m/fTpA9kr7T0e2Pv780pGhadLlP3b/B/9helPJSAAA
 ])

+CHECK_MIGRATE_FROM([9d2b5d7b86df00c30ac34fe87a3c20f1195bb2df],
+[H4sIANfQ6UMCA+2cW2+dR5aer61fQfSNbEDt1Pkwc9OeiZEY6HEDbc+ggSAQ6tjiRCYVkupp
+//s870dLoiTS3pQyyE2sbomHvb86rfUeqlbtf/r2v333/dm3f/nnP/7rD9/927f/+OSf//zt
+Nz9+e/bjN//0x2/P+lW7GC+er1eX48X1ky++fPLFi3b94uzi8ubs4vXLl2evL87/9+v17OzN
+f7///dnxgst9drV+aucX5xd/Pdvn6+W8Prter9pVu1nzrP989rt/+N2TL26f/tDTeNa/X55f
+XJ/9x/nNCx73t/Pr88uL52Nd3Vx//bf28vV68sXRsXcP+OA/nkAL8/Knsxfr779fF+Ny0vr5
+fPLFVx+Mc/bnf2tX10/evPPLt1/xbobxtolnb598PS5fLQ20vXr18ny0Gzp3fHvGk96+/aL9
+tD588/F2XnT2v9bPb194DOijV/7ywtvRvnnp7TR9eduzZ0cTX7395Ycj2+cv1/O5Xt602/U7
+n/eN5ebqkoV6s3R6z9m4vLhZFze8q7frD3r2/tLo5ddf8+DLq7vNfa2JPr587828d1z+9Opq
+XV+zGFfzfO+zm0s1Ry9ej5uz8frqiobP9hULp7affPHLiM/ns+MHXx0L+N33P3z75x/Pvvv+
+xz/dbfbs3775479++8OXT1sdLRubam6zLZNSr2ma3Frv3mwzkwsxVTeePns6Q3V1u5Cm8y3a
+1VvKtaYSlyu7tm5y7qH49vTZX57aXbop5s5/e28fgzcrhua9G96vsJxxxqw2SnOxmnb7yqcn
+dNunXV0JZo69m3Mmhe7mrMuUUKYNPtL3PKK6veuMtrSwrDdl9FWi8X3OsmMu3aa5QjA5VHtS
+t9u7bvvW+deux3TbVPo3QvK2OdOGTcmHsCPD93stE1oaKaQW6LZdYyXD9Nbda0huLhezTdaa
+nGpv07ZiVpnlpG7Xd92uLeTgqr3b7Y9y4W0WvLo6/6ld/awkfHZKFsx2J5DfJM6dSH7zylsM
+0JvvjdJ3M/a5QRf6L8F2DJ6Z9ExsNPHhNXvXdu+rzV0WM+nKqCt6npPtaNvFEm2oxZu0evrV
+tpemXm1vO+knwXZS258btUfb7c24Y16j9ZlPa/tzQ+9ou75pu4Akw2x3t+33Au6ndnG+1/XN
+LWn9Kn8+jjk/RvH7OPNN82/A+W137gD0/QR1POnyVaNzSgv1Zby4vF4X6sHr63X15Iv7Cevu
+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
+D6TSUF9iWAAA
+])
+
 AT_CLEANUP
============================================================
--- tests/t_sql_unpack.at	1bb994c7c10fcfafc2a0fc9b5dd7c45b5b315174
+++ tests/t_sql_gunzip.at	1e43988cec90fe9e16a51379b7b832cfc609d122
@@ -1,13 +1,9 @@
-AT_SETUP([sql functions unpack, unbase64])
+AT_SETUP([sql function gunzip (which replaced unpack)])
 MONOTONE_SETUP

-AT_CHECK(MONOTONE db execute "select unpack('H4sIAAAAAAAAAE2RQW4bMQwA7/uKRe5JJEoUyXOu/YFhGJREJgvEq8BetOjvq/bg9EyQMxju9uty1X1zux/riQSbM2lsiZxSzI6qhSVayh0KKSF51XJelvHZLzf7ud23sa8n65KtJSfOkUTcQyRVLMFRIPfgSt4C9vO/vW8gGJVMjiFxCIEqd0rgbowhZ6AOoQAUlQn80qN9rE8v1+NZj+N2f1pWv43rlK6F0MQ6ilnrUnWKZHMGjlLaPCEdVdJ5Wdf1GOvJpRbs4hGpWiVJUCFYVkabg8wkWbTbf9C3D93f7cd4f0Ch6kySCnTiktRQggALCSXClr2TYYnAD2ggDIXD1IyhUmIs3jqyxb9dHG3G6jXH/A1tYz9uW31tm9ZxXK5jH8fY7eXr98NiLnNjT1VxhgR2lQoxSw05okaKyQtJxIcFJ20cerbSySMDzq+CZbKoSVKtmDqgVjgvfwA6AWjaGQIAAA==')", [], [stdout], [ignore])
+AT_CHECK(MONOTONE db execute "select gunzip(X'1f8b08000000000000004d91416e1b310c00effb8a45ee49244a14c973aefd8161189444260bc4abc05eb4e8efabf6e0f44c903318eef6eb72d57d73bb1feb89049b33696c899c52cc8eaa85255aca1d0a292179d5725e96f1d92f37fbb9ddb7b1af27eb92ad2527ce9144dc4324552cc15120f7e04ade02f6f3bfbd6f2018954c8e217108812a774ae06e8c2167a00ea100149509fcd2a37dac4f2fd7e3598fe3767f5a56bf8deb94ae85d0c43a8a59eb52758a6473068e52da3c211d55d27959d7f518ebc9a516ece211a95a254950215856469b83cc2459b4db7fd0b70fdddfedc7787f40a1ea4c920a74e292d45082000b0925c296bd936189c00f68200c85c3d48ca152622cde3ab2c5bf5d1c6dc6ea35c7fc0d6d633f6e5b7d6d9bd6715cae631fc7d8ede5ebf7c3622e73634f5571860476950a314b0d39a2468ac90b49c48705276d1c7ab6d2c92303ceaf8265b2a84952ad983aa056382f7f003a0168da19020000')", [], [stdout], [ignore])

 AT_CHECK(QGREP('new_manifest \@<:@795cf87a1c37f7314f5aa6891e34d267a757fba6\@:>@' stdout))
 AT_CHECK(QGREP('from \@<:@7b675e9ed59eecd9bac3f4ef828196c2069d5a93\@:>@' stdout))

-AT_CHECK(MONOTONE db execute "select unbase64('aXQncyB0ZW4gcGFjZXMgbm9ydGh3ZXN0IG9mIHRoZSBwYWxtIHRyZWUuICBGT09CQVI=')", [], [stdout], [ignore])
-
-AT_CHECK(QGREP(FOOBAR stdout))
-
 AT_CLEANUP
============================================================
--- testsuite.at	7f433b14b3bd2a67eeacc49577a64e35d97ed02f
+++ testsuite.at	781e96c61a849b6f628e1aa691189b64f4d1bf08
@@ -629,7 +629,7 @@ m4_include(tests/t_db_execute.at)
 m4_include(tests/t_commit_log_3.at)
 m4_include(tests/t_at_sign.at)
 m4_include(tests/t_db_execute.at)
-m4_include(tests/t_sql_unpack.at)
+m4_include(tests/t_sql_gunzip.at)
 m4_include(tests/t_final_space.at)
 m4_include(tests/t_inodeprints.at)
 m4_include(tests/t_inodeprints_update.at)