[sql] SQLite patch to implement "smart" auto-vacuum.

SQLITE_FCNTL_CHUNK_SIZE can advise the VFS to resize files in quantum
amounts, to reduce fragmentation from tiny appends.  This change allows
a new PRAGMA auto_vacuum_slack_pages to provide auto_vacuum with a hint
to only rearrange pages when an entire quantum can be released.

BUG=698010

Review-Url: https://codereview.chromium.org/2732553002
Cr-Commit-Position: refs/heads/master@{#459847}
diff --git a/sql/sqlite_features_unittest.cc b/sql/sqlite_features_unittest.cc
index 6ddcc61..0f124af 100644
--- a/sql/sqlite_features_unittest.cc
+++ b/sql/sqlite_features_unittest.cc
@@ -361,4 +361,96 @@
 }
 #endif
 
+#if !defined(USE_SYSTEM_SQLITE)
+// Test that Chromium's patch to make auto_vacuum integrate with
+// SQLITE_FCNTL_CHUNK_SIZE is working.
+TEST_F(SQLiteFeaturesTest, SmartAutoVacuum) {
+  // Turn on auto_vacuum, and set the page size low to make results obvious.
+  // These settings require re-writing the database, which VACUUM does.
+  ASSERT_TRUE(db().Execute("PRAGMA auto_vacuum = FULL"));
+  ASSERT_TRUE(db().Execute("PRAGMA page_size = 1024"));
+  ASSERT_TRUE(db().Execute("VACUUM"));
+
+  // Code-coverage of the PRAGMA set/get implementation.
+  const char kPragmaSql[] = "PRAGMA auto_vacuum_slack_pages";
+  ASSERT_EQ("0", sql::test::ExecuteWithResult(&db(), kPragmaSql));
+  ASSERT_TRUE(db().Execute("PRAGMA auto_vacuum_slack_pages = 4"));
+  ASSERT_EQ("4", sql::test::ExecuteWithResult(&db(), kPragmaSql));
+  // Max out at 255.
+  ASSERT_TRUE(db().Execute("PRAGMA auto_vacuum_slack_pages = 1000"));
+  ASSERT_EQ("255", sql::test::ExecuteWithResult(&db(), kPragmaSql));
+  ASSERT_TRUE(db().Execute("PRAGMA auto_vacuum_slack_pages = 0"));
+
+  // With page_size=1024, the following will insert rows which take up an
+  // overflow page, plus a small header in a b-tree node.  An empty table takes
+  // a single page, so for small row counts each insert will add one page, and
+  // each delete will remove one page.
+  const char kCreateSql[] = "CREATE TABLE t (id INTEGER PRIMARY KEY, value)";
+  const char kInsertSql[] = "INSERT INTO t (value) VALUES (randomblob(980))";
+#if !defined(OS_WIN)
+  const char kDeleteSql[] = "DELETE FROM t WHERE id = (SELECT MIN(id) FROM t)";
+#endif
+
+  // This database will be 34 overflow pages plus the table's root page plus the
+  // SQLite header page plus the freelist page.
+  ASSERT_TRUE(db().Execute(kCreateSql));
+  {
+    sql::Statement s(db().GetUniqueStatement(kInsertSql));
+    for (int i = 0; i < 34; ++i) {
+      s.Reset(true);
+      ASSERT_TRUE(s.Run());
+    }
+  }
+  ASSERT_EQ("37", sql::test::ExecuteWithResult(&db(), "PRAGMA page_count"));
+
+  // http://sqlite.org/mmap.html indicates that Windows will silently fail when
+  // truncating a memory-mapped file.  That pretty much invalidates these tests
+  // against the actual file size.
+#if !defined(OS_WIN)
+  // Each delete will delete a single page, including crossing a
+  // multiple-of-four boundary.
+  {
+    sql::Statement s(db().GetUniqueStatement(kDeleteSql));
+    for (int i = 0; i < 5; ++i) {
+      int64_t file_size_before, file_size_after;
+      ASSERT_TRUE(base::GetFileSize(db_path(), &file_size_before));
+
+      s.Reset(true);
+      ASSERT_TRUE(s.Run());
+
+      ASSERT_TRUE(base::GetFileSize(db_path(), &file_size_after));
+      ASSERT_EQ(file_size_after, file_size_before - 1024);
+    }
+  }
+
+  // Turn on "smart" auto-vacuum to remove 4 pages at a time.
+  ASSERT_TRUE(db().Execute("PRAGMA auto_vacuum_slack_pages = 4"));
+
+  // No pages removed, then four deleted at once.
+  {
+    sql::Statement s(db().GetUniqueStatement(kDeleteSql));
+    for (int i = 0; i < 3; ++i) {
+      int64_t file_size_before, file_size_after;
+      ASSERT_TRUE(base::GetFileSize(db_path(), &file_size_before));
+
+      s.Reset(true);
+      ASSERT_TRUE(s.Run());
+
+      ASSERT_TRUE(base::GetFileSize(db_path(), &file_size_after));
+      ASSERT_EQ(file_size_after, file_size_before);
+    }
+
+    int64_t file_size_before, file_size_after;
+    ASSERT_TRUE(base::GetFileSize(db_path(), &file_size_before));
+
+    s.Reset(true);
+    ASSERT_TRUE(s.Run());
+
+    ASSERT_TRUE(base::GetFileSize(db_path(), &file_size_after));
+    ASSERT_EQ(file_size_after, file_size_before - 4096);
+  }
+#endif
+}
+#endif  // !defined(USE_SYSTEM_SQLITE)
+
 }  // namespace