sql: Feature flag for in-memory temporary storage.

This CL introduces SqlTempStoreMemory flag, which is intended to be
managed via Finch. When set, the flag causes sql::Database to run a
PRAGMA temp_store=MEMORY [1] query on every opened database.

This approach is intended to approximate the effect of building SQLite
with the SQLITE_TEMP_STORE=3 [2] macro, to measure the memory
consumption impact. Ideally, we'd test the macro directly, but //sql is
a core component of Chrome, so we can't load different versions of it
based on Finch.

[1] https://www.sqlite.org/pragma.html#pragma_temp_store
[2] https://www.sqlite.org/compile.html#temp_store

Bug: 875538
Change-Id: I537d90d763be1100503ed4bd2ada2ee19eb090bb
Reviewed-on: https://chromium-review.googlesource.com/1180530
Reviewed-by: Mark Pearson <[email protected]>
Reviewed-by: Chris Mumford <[email protected]>
Commit-Queue: Victor Costan <[email protected]>
Cr-Commit-Position: refs/heads/master@{#584652}
diff --git a/sql/BUILD.gn b/sql/BUILD.gn
index 98c6265bf..0a220ec 100644
--- a/sql/BUILD.gn
+++ b/sql/BUILD.gn
@@ -21,6 +21,8 @@
     "recovery.cc",
     "recovery.h",
     "sql_export.h",
+    "sql_features.cc",
+    "sql_features.h",
     "sql_memory_dump_provider.cc",
     "sql_memory_dump_provider.h",
     "statement.cc",
diff --git a/sql/database.cc b/sql/database.cc
index 66bf05b..ffe7c6b 100644
--- a/sql/database.cc
+++ b/sql/database.cc
@@ -33,6 +33,7 @@
 #include "sql/database_memory_dump_provider.h"
 #include "sql/initialization.h"
 #include "sql/meta_table.h"
+#include "sql/sql_features.h"
 #include "sql/statement.h"
 #include "sql/vfs_wrapper.h"
 #include "third_party/sqlite/sqlite3.h"
@@ -1674,6 +1675,12 @@
     ignore_result(Execute("PRAGMA locking_mode=EXCLUSIVE"));
   }
 
+  if (base::FeatureList::IsEnabled(features::kSqlTempStoreMemory)) {
+    err = ExecuteAndReturnErrorCode("PRAGMA temp_store=MEMORY");
+    // This operates on in-memory configuration, so it should not fail.
+    DCHECK_EQ(err, SQLITE_OK) << "Failed switching to in-RAM temporary storage";
+  }
+
   // http://www.sqlite.org/pragma.html#pragma_journal_mode
   // DELETE (default) - delete -journal file to commit.
   // TRUNCATE - truncate -journal file to commit.
@@ -1681,7 +1688,7 @@
   // TRUNCATE should be faster than DELETE because it won't need directory
   // changes for each transaction.  PERSIST may break the spirit of using
   // secure_delete.
-  ignore_result(Execute("PRAGMA journal_mode = TRUNCATE"));
+  ignore_result(Execute("PRAGMA journal_mode=TRUNCATE"));
 
   const base::TimeDelta kBusyTimeout =
       base::TimeDelta::FromSeconds(kBusyTimeoutSeconds);
@@ -1728,7 +1735,7 @@
   // 64-bit platforms.
   size_t mmap_size = mmap_disabled_ ? 0 : GetAppropriateMmapSize();
   std::string mmap_sql =
-      base::StringPrintf("PRAGMA mmap_size = %" PRIuS, mmap_size);
+      base::StringPrintf("PRAGMA mmap_size=%" PRIuS, mmap_size);
   ignore_result(Execute(mmap_sql.c_str()));
 
   // Determine if memory-mapping has actually been enabled.  The Execute() above
@@ -1889,7 +1896,7 @@
   // allows SQLite to process through certain cases of corruption.
   // Failing to set this pragma probably means that the database is
   // beyond recovery.
-  static const char kWritableSchemaSql[] = "PRAGMA writable_schema = ON";
+  static const char kWritableSchemaSql[] = "PRAGMA writable_schema=ON";
   if (!Execute(kWritableSchemaSql))
     return false;
 
@@ -1909,7 +1916,7 @@
   }
 
   // Best effort to put things back as they were before.
-  static const char kNoWritableSchemaSql[] = "PRAGMA writable_schema = OFF";
+  static const char kNoWritableSchemaSql[] = "PRAGMA writable_schema=OFF";
   ignore_result(Execute(kNoWritableSchemaSql));
 
   return ret;
diff --git a/sql/database_unittest.cc b/sql/database_unittest.cc
index 57a4505..78c5bfb 100644
--- a/sql/database_unittest.cc
+++ b/sql/database_unittest.cc
@@ -13,12 +13,14 @@
 #include "base/macros.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "base/trace_event/process_memory_dump.h"
 #include "build/build_config.h"
 #include "sql/database.h"
 #include "sql/database_memory_dump_provider.h"
 #include "sql/meta_table.h"
+#include "sql/sql_features.h"
 #include "sql/statement.h"
 #include "sql/test/error_callback_support.h"
 #include "sql/test/scoped_error_expecter.h"
@@ -100,7 +102,6 @@
 
 namespace {
 
-using sql::test::ExecuteWithResults;
 using sql::test::ExecuteWithResult;
 
 // Helper to return the count of items in sqlite_master.  Return -1 in
@@ -1618,4 +1619,22 @@
 #endif  // !defined(OS_ANDROID) && !defined(OS_IOS) && !defined(OS_FUCHSIA)
 }
 
+// Verify that Raze() can handle an empty file.  SQLite should treat
+// this as an empty database.
+TEST_F(SQLDatabaseTest, SqlTempMemoryFeatureFlagDefault) {
+  EXPECT_EQ("0", ExecuteWithResult(&db(), "PRAGMA temp_store"))
+      << "temp_store should not be set by default";
+}
+
+TEST_F(SQLDatabaseTest, SqlTempMemoryFeatureFlagEnabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kSqlTempStoreMemory);
+
+  db().Close();
+
+  ASSERT_TRUE(db().Open(db_path()));
+  EXPECT_EQ("2", ExecuteWithResult(&db(), "PRAGMA temp_store"))
+      << "temp_store should be set by the feature flag SqlTempStoreMemory";
+}
+
 }  // namespace sql
diff --git a/sql/sql_features.cc b/sql/sql_features.cc
new file mode 100644
index 0000000..c7cf33e
--- /dev/null
+++ b/sql/sql_features.cc
@@ -0,0 +1,23 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sql/sql_features.h"
+
+namespace sql {
+
+namespace features {
+
+// SQLite databases only use RAM for temporary storage.
+//
+// Enabling this feature matches the SQLITE_TEMP_STORE=3 build option, which is
+// used on Android.
+//
+// TODO(pwnall): After the memory impact of the config change is assessed, land
+//               https://crrev.com/c/1146493 and remove this flag.
+const base::Feature kSqlTempStoreMemory{"SqlTempStoreMemory",
+                                        base::FEATURE_DISABLED_BY_DEFAULT};
+
+}  // namespace features
+
+}  // namespace sql
diff --git a/sql/sql_features.h b/sql/sql_features.h
new file mode 100644
index 0000000..4b0c947
--- /dev/null
+++ b/sql/sql_features.h
@@ -0,0 +1,21 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SQL_SQL_FEATURES_H_
+#define SQL_SQL_FEATURES_H_
+
+#include "base/feature_list.h"
+#include "sql/sql_export.h"
+
+namespace sql {
+
+namespace features {
+
+SQL_EXPORT extern const base::Feature kSqlTempStoreMemory;
+
+}  // namespace features
+
+}  // namespace sql
+
+#endif  // SQL_SQL_FEATURES_H_