[sql] Scoped recovery framework.

sql::Recovery is intended to be used within a sql::Connection error
callback to either recover the database (*) or indicate that the
database is unrecoverable and should be razed.  The intend is that the
code should either progress to a valid database which is composed of
data recovered from the original (likely corrupt) database, or a valid
database which is empty.

This is just the framework without the SQLite-level data-recovery
virtual table.

BUG=109482

Review URL: https://chromiumcodereview.appspot.com/19281002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@212607 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/sql/connection.cc b/sql/connection.cc
index e7b6fe1..4550b0e5 100644
--- a/sql/connection.cc
+++ b/sql/connection.cc
@@ -94,6 +94,21 @@
   return rc;
 }
 
+// Be very strict on attachment point.  SQLite can handle a much wider
+// character set with appropriate quoting, but Chromium code should
+// just use clean names to start with.
+bool ValidAttachmentPoint(const char* attachment_point) {
+  for (size_t i = 0; attachment_point[i]; ++i) {
+    if (!((attachment_point[i] >= '0' && attachment_point[i] <= '9') ||
+          (attachment_point[i] >= 'a' && attachment_point[i] <= 'z') ||
+          (attachment_point[i] >= 'A' && attachment_point[i] <= 'Z') ||
+          attachment_point[i] == '_')) {
+      return false;
+    }
+  }
+  return true;
+}
+
 }  // namespace
 
 namespace sql {
@@ -206,6 +221,10 @@
   return OpenInternal(":memory:", NO_RETRY);
 }
 
+bool Connection::OpenTemporary() {
+  return OpenInternal("", NO_RETRY);
+}
+
 void Connection::CloseInternal(bool forced) {
   // TODO(shess): Calling "PRAGMA journal_mode = DELETE" at this point
   // will delete the -journal file.  For ChromiumOS or other more
@@ -442,9 +461,7 @@
   }
 
   // Raze() cannot run in a transaction.
-  while (transaction_nesting_) {
-    RollbackTransaction();
-  }
+  RollbackAllTransactions();
 
   bool result = Raze();
 
@@ -458,6 +475,21 @@
   return result;
 }
 
+void Connection::Poison() {
+  if (!db_) {
+    DLOG_IF(FATAL, !poisoned_) << "Cannot poison null db";
+    return;
+  }
+
+  RollbackAllTransactions();
+  CloseInternal(true);
+
+  // Mark the database so that future API calls fail appropriately,
+  // but don't DCHECK (because after calling this function they are
+  // expected to fail).
+  poisoned_ = true;
+}
+
 // TODO(shess): To the extent possible, figure out the optimal
 // ordering for these deletes which will prevent other connections
 // from seeing odd behavior.  For instance, it may be necessary to
@@ -543,6 +575,35 @@
   return commit.Run();
 }
 
+void Connection::RollbackAllTransactions() {
+  if (transaction_nesting_ > 0) {
+    transaction_nesting_ = 0;
+    DoRollback();
+  }
+}
+
+bool Connection::AttachDatabase(const base::FilePath& other_db_path,
+                                const char* attachment_point) {
+  DCHECK(ValidAttachmentPoint(attachment_point));
+
+  Statement s(GetUniqueStatement("ATTACH DATABASE ? AS ?"));
+#if OS_WIN
+  s.BindString16(0, other_db_path.value());
+#else
+  s.BindString(0, other_db_path.value());
+#endif
+  s.BindString(1, attachment_point);
+  return s.Run();
+}
+
+bool Connection::DetachDatabase(const char* attachment_point) {
+  DCHECK(ValidAttachmentPoint(attachment_point));
+
+  Statement s(GetUniqueStatement("DETACH DATABASE ?"));
+  s.BindString(0, attachment_point);
+  return s.Run();
+}
+
 int Connection::ExecuteAndReturnErrorCode(const char* sql) {
   AssertIOAllowed();
   if (!db_) {