// Copyright 2019 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 #include #include #include #include "base/files/scoped_temp_dir.h" #include "base/strings/stringprintf.h" #include "sql/database.h" #include "sql/statement.h" #include "sql/test/database_test_peer.h" #include "sql/test/scoped_error_expecter.h" #include "sql/test/test_helpers.h" #include "sql/transaction.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/sqlite/sqlite3.h" namespace sql { namespace recover { class RecoverModuleTest : public testing::Test { public: ~RecoverModuleTest() override = default; void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); ASSERT_TRUE( db_.Open(temp_dir_.GetPath().AppendASCII("recovery_test.sqlite"))); ASSERT_TRUE(DatabaseTestPeer::EnableRecoveryExtension(&db_)); } protected: base::ScopedTempDir temp_dir_; sql::Database db_{sql::DatabaseOptions{ .enable_virtual_tables_discouraged = true, }}; }; TEST_F(RecoverModuleTest, CreateVtable) { ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)")); EXPECT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing " "USING recover(backing, t TEXT)")); } TEST_F(RecoverModuleTest, CreateVtableWithDatabaseSpecifier) { ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)")); EXPECT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing " "USING recover(main.backing, t TEXT)")); } TEST_F(RecoverModuleTest, CreateVtableOnSqliteSchema) { ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)")); EXPECT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing USING recover(" "sqlite_schema, type TEXT, name TEXT, tbl_name TEXT, " "rootpage INTEGER, sql TEXT)")); } TEST_F(RecoverModuleTest, CreateVtableFailsOnNonTempTable) { ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)")); { sql::test::ScopedErrorExpecter error_expecter; error_expecter.ExpectError(SQLITE_ERROR); EXPECT_FALSE(db_.Execute( "CREATE VIRTUAL TABLE recover_backing USING recover(backing, t TEXT)")); EXPECT_TRUE(error_expecter.SawExpectedErrors()); } } TEST_F(RecoverModuleTest, CreateVtableFailsOnMissingTable) { ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)")); { sql::test::ScopedErrorExpecter error_expecter; error_expecter.ExpectError(SQLITE_CORRUPT); EXPECT_FALSE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_missing " "USING recover(missing, t TEXT)")); EXPECT_TRUE(error_expecter.SawExpectedErrors()); } } TEST_F(RecoverModuleTest, CreateVtableFailsOnMissingDatabase) { ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)")); { sql::test::ScopedErrorExpecter error_expecter; error_expecter.ExpectError(SQLITE_CORRUPT); EXPECT_FALSE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing " "USING recover(db.backing, t TEXT)")); EXPECT_TRUE(error_expecter.SawExpectedErrors()); } } TEST_F(RecoverModuleTest, CreateVtableFailsOnTableWithInvalidQualifier) { ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)")); { sql::test::ScopedErrorExpecter error_expecter; error_expecter.ExpectError(SQLITE_CORRUPT); EXPECT_FALSE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing " "USING recover(backing invalid, t TEXT)")); EXPECT_TRUE(error_expecter.SawExpectedErrors()); } } TEST_F(RecoverModuleTest, CreateVtableFailsOnMissingTableName) { ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)")); { sql::test::ScopedErrorExpecter error_expecter; error_expecter.ExpectError(SQLITE_ERROR); EXPECT_FALSE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing " "USING recover(main., t TEXT)")); EXPECT_TRUE(error_expecter.SawExpectedErrors()); } } TEST_F(RecoverModuleTest, CreateVtableFailsOnMissingSchemaSpec) { ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)")); { sql::test::ScopedErrorExpecter error_expecter; error_expecter.ExpectError(SQLITE_ERROR); EXPECT_FALSE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing " "USING recover(backing)")); EXPECT_TRUE(error_expecter.SawExpectedErrors()); } } TEST_F(RecoverModuleTest, CreateVtableFailsOnMissingDbName) { ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)")); { sql::test::ScopedErrorExpecter error_expecter; error_expecter.ExpectError(SQLITE_ERROR); EXPECT_FALSE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing " "USING recover(.backing)")); EXPECT_TRUE(error_expecter.SawExpectedErrors()); } } TEST_F(RecoverModuleTest, ColumnTypeMappingAny) { ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)")); EXPECT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing " "USING recover(backing, t ANY)")); sql::test::ColumnInfo column_info = sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "t"); EXPECT_EQ("(nullptr)", column_info.data_type); EXPECT_EQ("BINARY", column_info.collation_sequence); EXPECT_FALSE(column_info.has_non_null_constraint); EXPECT_FALSE(column_info.is_in_primary_key); EXPECT_FALSE(column_info.is_auto_incremented); } TEST_F(RecoverModuleTest, ColumnTypeMappingAnyNotNull) { ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)")); EXPECT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing " "USING recover(backing, t ANY NOT NULL)")); sql::test::ColumnInfo column_info = sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "t"); EXPECT_EQ("(nullptr)", column_info.data_type); EXPECT_EQ("BINARY", column_info.collation_sequence); EXPECT_TRUE(column_info.has_non_null_constraint); EXPECT_FALSE(column_info.is_in_primary_key); EXPECT_FALSE(column_info.is_auto_incremented); } TEST_F(RecoverModuleTest, ColumnTypeMappingAnyStrict) { ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)")); { sql::test::ScopedErrorExpecter error_expecter; error_expecter.ExpectError(SQLITE_ERROR); EXPECT_FALSE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing " "USING recover(backing, t ANY STRICT)")); EXPECT_TRUE(error_expecter.SawExpectedErrors()); } } TEST_F(RecoverModuleTest, ColumnTypeExtraKeyword) { ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)")); { sql::test::ScopedErrorExpecter error_expecter; error_expecter.ExpectError(SQLITE_ERROR); EXPECT_FALSE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing " "USING recover(backing, t INTEGER SOMETHING)")); EXPECT_TRUE(error_expecter.SawExpectedErrors()); } } TEST_F(RecoverModuleTest, ColumnTypeNotNullExtraKeyword) { ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)")); { sql::test::ScopedErrorExpecter error_expecter; error_expecter.ExpectError(SQLITE_ERROR); EXPECT_FALSE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing " "USING recover(backing, t INTEGER NOT NULL SOMETHING)")); EXPECT_TRUE(error_expecter.SawExpectedErrors()); } } TEST_F(RecoverModuleTest, ColumnTypeDoubleTypes) { ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)")); { sql::test::ScopedErrorExpecter error_expecter; error_expecter.ExpectError(SQLITE_ERROR); EXPECT_FALSE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing " "USING recover(backing, t INTEGER FLOAT)")); EXPECT_TRUE(error_expecter.SawExpectedErrors()); } } TEST_F(RecoverModuleTest, ColumnTypeNotNullDoubleTypes) { ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)")); { sql::test::ScopedErrorExpecter error_expecter; error_expecter.ExpectError(SQLITE_ERROR); EXPECT_FALSE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing USING recover(" "backing, t INTEGER NOT NULL TEXT)")); EXPECT_TRUE(error_expecter.SawExpectedErrors()); } } class RecoverModuleColumnTypeMappingTest : public RecoverModuleTest, public ::testing::WithParamInterface< std::tuple> { public: ~RecoverModuleColumnTypeMappingTest() override = default; void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); ASSERT_TRUE( db_.Open(temp_dir_.GetPath().AppendASCII("recovery_test.sqlite"))); ASSERT_TRUE(DatabaseTestPeer::EnableRecoveryExtension(&db_)); std::string sql = base::StringPrintf("CREATE TABLE backing(data %s)", SchemaType()); ASSERT_TRUE(db_.Execute(sql.c_str())); } void CreateRecoveryTable(const char* suffix) { std::string sql = base::StringPrintf( "CREATE VIRTUAL TABLE temp.recover_backing " "USING recover(backing, data %s%s)", SchemaType(), suffix); ASSERT_TRUE(db_.Execute(sql.c_str())); } const char* SchemaType() const { return std::get<0>(GetParam()); } const char* ExpectedType() const { return std::get<1>(GetParam()); } bool IsAlwaysNonNull() const { return std::get<2>(GetParam()); } protected: base::ScopedTempDir temp_dir_; sql::Database db_; }; TEST_P(RecoverModuleColumnTypeMappingTest, Unqualified) { CreateRecoveryTable(""); sql::test::ColumnInfo column_info = sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "data"); EXPECT_EQ(ExpectedType(), column_info.data_type); EXPECT_EQ("BINARY", column_info.collation_sequence); EXPECT_EQ(IsAlwaysNonNull(), column_info.has_non_null_constraint); EXPECT_FALSE(column_info.is_in_primary_key); EXPECT_FALSE(column_info.is_auto_incremented); } TEST_P(RecoverModuleColumnTypeMappingTest, NotNull) { CreateRecoveryTable(" NOT NULL"); sql::test::ColumnInfo column_info = sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "data"); EXPECT_EQ(ExpectedType(), column_info.data_type); EXPECT_EQ("BINARY", column_info.collation_sequence); EXPECT_TRUE(column_info.has_non_null_constraint); EXPECT_FALSE(column_info.is_in_primary_key); EXPECT_FALSE(column_info.is_auto_incremented); } TEST_P(RecoverModuleColumnTypeMappingTest, Strict) { CreateRecoveryTable(" STRICT"); sql::test::ColumnInfo column_info = sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "data"); EXPECT_EQ(ExpectedType(), column_info.data_type); EXPECT_EQ("BINARY", column_info.collation_sequence); EXPECT_EQ(IsAlwaysNonNull(), column_info.has_non_null_constraint); EXPECT_FALSE(column_info.is_in_primary_key); EXPECT_FALSE(column_info.is_auto_incremented); } TEST_P(RecoverModuleColumnTypeMappingTest, StrictNotNull) { CreateRecoveryTable(" STRICT NOT NULL"); sql::test::ColumnInfo column_info = sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "data"); EXPECT_EQ(ExpectedType(), column_info.data_type); EXPECT_EQ("BINARY", column_info.collation_sequence); EXPECT_TRUE(column_info.has_non_null_constraint); EXPECT_FALSE(column_info.is_in_primary_key); EXPECT_FALSE(column_info.is_auto_incremented); } INSTANTIATE_TEST_SUITE_P( All, RecoverModuleColumnTypeMappingTest, ::testing::Values(std::make_tuple("TEXT", "TEXT", false), std::make_tuple("INTEGER", "INTEGER", false), std::make_tuple("FLOAT", "FLOAT", false), std::make_tuple("BLOB", "BLOB", false), std::make_tuple("NUMERIC", "NUMERIC", false), std::make_tuple("ROWID", "INTEGER", true))); namespace { void GenerateAlteredTable(sql::Database* db) { ASSERT_TRUE(db->Execute("CREATE TABLE altered(t TEXT)")); ASSERT_TRUE(db->Execute("INSERT INTO altered VALUES('a')")); ASSERT_TRUE(db->Execute("INSERT INTO altered VALUES('b')")); ASSERT_TRUE(db->Execute("INSERT INTO altered VALUES('c')")); ASSERT_TRUE(db->Execute( "ALTER TABLE altered ADD COLUMN i INTEGER NOT NULL DEFAULT 10")); ASSERT_TRUE(db->Execute("INSERT INTO altered VALUES('d', 5)")); } } // namespace TEST_F(RecoverModuleTest, ReadFromAlteredTableNullDefaults) { GenerateAlteredTable(&db_); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_altered " "USING recover(altered, t TEXT, i INTEGER)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT t, i FROM recover_altered ORDER BY rowid")); ASSERT_TRUE(statement.Step()); EXPECT_EQ("a", statement.ColumnString(0)); EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(1)); ASSERT_TRUE(statement.Step()); EXPECT_EQ("b", statement.ColumnString(0)); EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(1)); ASSERT_TRUE(statement.Step()); EXPECT_EQ("c", statement.ColumnString(0)); EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(1)); ASSERT_TRUE(statement.Step()); EXPECT_EQ("d", statement.ColumnString(0)); EXPECT_EQ(5, statement.ColumnInt(1)); EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, ReadFromAlteredTableSkipsNulls) { GenerateAlteredTable(&db_); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_altered " "USING recover(altered, t TEXT, i INTEGER NOT NULL)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT t, i FROM recover_altered ORDER BY rowid")); ASSERT_TRUE(statement.Step()); EXPECT_EQ("d", statement.ColumnString(0)); EXPECT_EQ(5, statement.ColumnInt(1)); EXPECT_FALSE(statement.Step()); } namespace { void GenerateSizedTable(sql::Database* db, int row_count, const std::string& prefix) { ASSERT_TRUE(db->Execute("CREATE TABLE sized(t TEXT, i INTEGER)")); sql::Transaction transaction(db); ASSERT_TRUE(transaction.Begin()); sql::Statement statement( db->GetUniqueStatement("INSERT INTO sized VALUES(?, ?)")); for (int i = 0; i < row_count; ++i) { statement.BindString(0, base::StringPrintf("%s%d", prefix.c_str(), i)); statement.BindInt(1, i); ASSERT_TRUE(statement.Run()); statement.Reset(/* clear_bound_vars= */ true); } ASSERT_TRUE(transaction.Commit()); } } // namespace TEST_F(RecoverModuleTest, LeafNodes) { GenerateSizedTable(&db_, 10, "Leaf-node-generating line "); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_sized " "USING recover(sized, t TEXT, i INTEGER NOT NULL)")); sql::Statement statement( db_.GetUniqueStatement("SELECT t, i FROM recover_sized ORDER BY rowid")); for (int i = 0; i < 10; ++i) { ASSERT_TRUE(statement.Step()); EXPECT_EQ(base::StringPrintf("Leaf-node-generating line %d", i), statement.ColumnString(0)); EXPECT_EQ(i, statement.ColumnInt(1)); } EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, EmptyTable) { GenerateSizedTable(&db_, 0, ""); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_sized " "USING recover(sized, t TEXT, i INTEGER NOT NULL)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT rowid, t, i FROM recover_sized ORDER BY rowid")); EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, SingleLevelInteriorNodes) { GenerateSizedTable(&db_, 100, "Interior-node-generating line "); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_sized " "USING recover(sized, t TEXT, i INTEGER NOT NULL)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT rowid, t, i FROM recover_sized ORDER BY rowid")); for (int i = 0; i < 100; ++i) { ASSERT_TRUE(statement.Step()); EXPECT_EQ(i + 1, statement.ColumnInt(0)); EXPECT_EQ(base::StringPrintf("Interior-node-generating line %d", i), statement.ColumnString(1)); EXPECT_EQ(i, statement.ColumnInt(2)); } EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, MultiLevelInteriorNodes) { GenerateSizedTable(&db_, 5000, "Interior-node-generating line "); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_sized " "USING recover(sized, t TEXT, i INTEGER NOT NULL)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT rowid, t, i FROM recover_sized ORDER BY rowid")); for (int i = 0; i < 5000; ++i) { ASSERT_TRUE(statement.Step()); EXPECT_EQ(i + 1, statement.ColumnInt(0)); EXPECT_EQ(base::StringPrintf("Interior-node-generating line %d", i), statement.ColumnString(1)); EXPECT_EQ(i, statement.ColumnInt(2)); } EXPECT_FALSE(statement.Step()); } namespace { void GenerateTypesTable(sql::Database* db) { ASSERT_TRUE(db->Execute("CREATE TABLE types(rowtype TEXT, value)")); ASSERT_TRUE(db->Execute("INSERT INTO types VALUES('NULL', NULL)")); ASSERT_TRUE(db->Execute("INSERT INTO types VALUES('INTEGER', 17)")); ASSERT_TRUE(db->Execute("INSERT INTO types VALUES('FLOAT', 3.1415927)")); ASSERT_TRUE(db->Execute("INSERT INTO types VALUES('TEXT', 'This is text')")); ASSERT_TRUE(db->Execute( "INSERT INTO types VALUES('BLOB', CAST('This is a blob' AS BLOB))")); } } // namespace TEST_F(RecoverModuleTest, Any) { GenerateTypesTable(&db_); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_types " "USING recover(types, rowtype TEXT, value ANY)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT rowid, rowtype, value FROM recover_types")); ASSERT_TRUE(statement.Step()); EXPECT_EQ(1, statement.ColumnInt(0)); EXPECT_EQ("NULL", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(2)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(2, statement.ColumnInt(0)); EXPECT_EQ("INTEGER", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kInteger, statement.GetColumnType(2)); EXPECT_EQ(17, statement.ColumnInt(2)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(3, statement.ColumnInt(0)); EXPECT_EQ("FLOAT", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kFloat, statement.GetColumnType(2)); EXPECT_DOUBLE_EQ(3.1415927, statement.ColumnDouble(2)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(4, statement.ColumnInt(0)); EXPECT_EQ("TEXT", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kText, statement.GetColumnType(2)); EXPECT_EQ("This is text", statement.ColumnString(2)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(5, statement.ColumnInt(0)); EXPECT_EQ("BLOB", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kBlob, statement.GetColumnType(2)); std::string blob_text; ASSERT_TRUE(statement.ColumnBlobAsString(2, &blob_text)); EXPECT_EQ("This is a blob", blob_text); EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, Integers) { GenerateTypesTable(&db_); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_types " "USING recover(types, rowtype TEXT, value INTEGER)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT rowid, rowtype, value FROM recover_types")); ASSERT_TRUE(statement.Step()); EXPECT_EQ(1, statement.ColumnInt(0)); EXPECT_EQ("NULL", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(2)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(2, statement.ColumnInt(0)); EXPECT_EQ("INTEGER", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kInteger, statement.GetColumnType(2)); EXPECT_EQ(17, statement.ColumnInt(2)); EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, NonNullIntegers) { GenerateTypesTable(&db_); ASSERT_TRUE(db_.Execute( "CREATE VIRTUAL TABLE temp.recover_types " "USING recover(types, rowtype TEXT, value INTEGER NOT NULL)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT rowid, rowtype, value FROM recover_types")); ASSERT_TRUE(statement.Step()); EXPECT_EQ(2, statement.ColumnInt(0)); EXPECT_EQ("INTEGER", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kInteger, statement.GetColumnType(2)); EXPECT_EQ(17, statement.ColumnInt(2)); EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, Floats) { GenerateTypesTable(&db_); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_types " "USING recover(types, rowtype TEXT, value FLOAT)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT rowid, rowtype, value FROM recover_types")); ASSERT_TRUE(statement.Step()); EXPECT_EQ(1, statement.ColumnInt(0)); EXPECT_EQ("NULL", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(2)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(2, statement.ColumnInt(0)); EXPECT_EQ("INTEGER", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kInteger, statement.GetColumnType(2)); EXPECT_EQ(17, statement.ColumnInt(2)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(3, statement.ColumnInt(0)); EXPECT_EQ("FLOAT", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kFloat, statement.GetColumnType(2)); EXPECT_DOUBLE_EQ(3.1415927, statement.ColumnDouble(2)); EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, NonNullFloats) { GenerateTypesTable(&db_); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_types " "USING recover(types, rowtype TEXT, value FLOAT NOT NULL)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT rowid, rowtype, value FROM recover_types")); ASSERT_TRUE(statement.Step()); EXPECT_EQ(2, statement.ColumnInt(0)); EXPECT_EQ("INTEGER", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kInteger, statement.GetColumnType(2)); EXPECT_EQ(17, statement.ColumnInt(2)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(3, statement.ColumnInt(0)); EXPECT_EQ("FLOAT", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kFloat, statement.GetColumnType(2)); EXPECT_DOUBLE_EQ(3.1415927, statement.ColumnDouble(2)); EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, FloatsStrict) { GenerateTypesTable(&db_); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_types " "USING recover(types, rowtype TEXT, value FLOAT STRICT)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT rowid, rowtype, value FROM recover_types")); ASSERT_TRUE(statement.Step()); EXPECT_EQ(1, statement.ColumnInt(0)); EXPECT_EQ("NULL", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(2)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(3, statement.ColumnInt(0)); EXPECT_EQ("FLOAT", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kFloat, statement.GetColumnType(2)); EXPECT_DOUBLE_EQ(3.1415927, statement.ColumnDouble(2)); EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, NonNullFloatsStrict) { GenerateTypesTable(&db_); ASSERT_TRUE(db_.Execute( "CREATE VIRTUAL TABLE temp.recover_types " "USING recover(types, rowtype TEXT, value FLOAT STRICT NOT NULL)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT rowid, rowtype, value FROM recover_types")); ASSERT_TRUE(statement.Step()); EXPECT_EQ(3, statement.ColumnInt(0)); EXPECT_EQ("FLOAT", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kFloat, statement.GetColumnType(2)); EXPECT_DOUBLE_EQ(3.1415927, statement.ColumnDouble(2)); EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, Texts) { GenerateTypesTable(&db_); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_types " "USING recover(types, rowtype TEXT, value TEXT)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT rowid, rowtype, value FROM recover_types")); ASSERT_TRUE(statement.Step()); EXPECT_EQ(1, statement.ColumnInt(0)); EXPECT_EQ("NULL", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(2)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(4, statement.ColumnInt(0)); EXPECT_EQ("TEXT", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kText, statement.GetColumnType(2)); EXPECT_EQ("This is text", statement.ColumnString(2)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(5, statement.ColumnInt(0)); EXPECT_EQ("BLOB", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kBlob, statement.GetColumnType(2)); std::string blob_text; ASSERT_TRUE(statement.ColumnBlobAsString(2, &blob_text)); EXPECT_EQ("This is a blob", blob_text); EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, NonNullTexts) { GenerateTypesTable(&db_); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_types " "USING recover(types, rowtype TEXT, value TEXT NOT NULL)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT rowid, rowtype, value FROM recover_types")); ASSERT_TRUE(statement.Step()); EXPECT_EQ(4, statement.ColumnInt(0)); EXPECT_EQ("TEXT", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kText, statement.GetColumnType(2)); EXPECT_EQ("This is text", statement.ColumnString(2)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(5, statement.ColumnInt(0)); EXPECT_EQ("BLOB", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kBlob, statement.GetColumnType(2)); std::string blob_text; ASSERT_TRUE(statement.ColumnBlobAsString(2, &blob_text)); EXPECT_EQ("This is a blob", blob_text); EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, TextsStrict) { GenerateTypesTable(&db_); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_types " "USING recover(types, rowtype TEXT, value TEXT STRICT)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT rowid, rowtype, value FROM recover_types")); ASSERT_TRUE(statement.Step()); EXPECT_EQ(1, statement.ColumnInt(0)); EXPECT_EQ("NULL", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(2)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(4, statement.ColumnInt(0)); EXPECT_EQ("TEXT", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kText, statement.GetColumnType(2)); EXPECT_EQ("This is text", statement.ColumnString(2)); EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, NonNullTextsStrict) { GenerateTypesTable(&db_); ASSERT_TRUE(db_.Execute( "CREATE VIRTUAL TABLE temp.recover_types " "USING recover(types, rowtype TEXT, value TEXT STRICT NOT NULL)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT rowid, rowtype, value FROM recover_types")); ASSERT_TRUE(statement.Step()); EXPECT_EQ(4, statement.ColumnInt(0)); EXPECT_EQ("TEXT", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kText, statement.GetColumnType(2)); EXPECT_EQ("This is text", statement.ColumnString(2)); EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, Blobs) { GenerateTypesTable(&db_); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_types " "USING recover(types, rowtype TEXT, value BLOB)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT rowid, rowtype, value FROM recover_types")); ASSERT_TRUE(statement.Step()); EXPECT_EQ(1, statement.ColumnInt(0)); EXPECT_EQ("NULL", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(2)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(5, statement.ColumnInt(0)); EXPECT_EQ("BLOB", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kBlob, statement.GetColumnType(2)); std::string blob_text; ASSERT_TRUE(statement.ColumnBlobAsString(2, &blob_text)); EXPECT_EQ("This is a blob", blob_text); EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, NonNullBlobs) { GenerateTypesTable(&db_); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_types " "USING recover(types, rowtype TEXT, value BLOB NOT NULL)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT rowid, rowtype, value FROM recover_types")); ASSERT_TRUE(statement.Step()); EXPECT_EQ(5, statement.ColumnInt(0)); EXPECT_EQ("BLOB", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kBlob, statement.GetColumnType(2)); std::string blob_text; ASSERT_TRUE(statement.ColumnBlobAsString(2, &blob_text)); EXPECT_EQ("This is a blob", blob_text); EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, AnyNonNull) { GenerateTypesTable(&db_); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_types " "USING recover(types, rowtype TEXT, value ANY NOT NULL)")); sql::Statement statement(db_.GetUniqueStatement( "SELECT rowid, rowtype, value FROM recover_types")); ASSERT_TRUE(statement.Step()); EXPECT_EQ(2, statement.ColumnInt(0)); EXPECT_EQ("INTEGER", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kInteger, statement.GetColumnType(2)); EXPECT_EQ(17, statement.ColumnInt(2)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(3, statement.ColumnInt(0)); EXPECT_EQ("FLOAT", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kFloat, statement.GetColumnType(2)); EXPECT_DOUBLE_EQ(3.1415927, statement.ColumnDouble(2)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(4, statement.ColumnInt(0)); EXPECT_EQ("TEXT", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kText, statement.GetColumnType(2)); EXPECT_EQ("This is text", statement.ColumnString(2)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(5, statement.ColumnInt(0)); EXPECT_EQ("BLOB", statement.ColumnString(1)); EXPECT_EQ(sql::ColumnType::kBlob, statement.GetColumnType(2)); std::string blob_text; ASSERT_TRUE(statement.ColumnBlobAsString(2, &blob_text)); EXPECT_EQ("This is a blob", blob_text); EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, RowidAlias) { GenerateTypesTable(&db_); // The id column is an alias for rowid, and its values get serialized as NULL. ASSERT_TRUE(db_.Execute( "CREATE TABLE types2(id INTEGER PRIMARY KEY, rowtype TEXT, value)")); ASSERT_TRUE( db_.Execute("INSERT INTO types2(id, rowtype, value) " "SELECT rowid, rowtype, value FROM types WHERE true")); ASSERT_TRUE(db_.Execute( "CREATE VIRTUAL TABLE temp.recover_types2 " "USING recover(types2, id ROWID NOT NULL, rowtype TEXT, value ANY)")); sql::Statement statement( db_.GetUniqueStatement("SELECT id, rowid, rowtype, value FROM types2")); ASSERT_TRUE(statement.Step()); EXPECT_EQ(1, statement.ColumnInt(0)); EXPECT_EQ(1, statement.ColumnInt(1)); EXPECT_EQ("NULL", statement.ColumnString(2)); EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(3)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(2, statement.ColumnInt(0)); EXPECT_EQ(2, statement.ColumnInt(1)); EXPECT_EQ("INTEGER", statement.ColumnString(2)); EXPECT_EQ(17, statement.ColumnInt(3)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(3, statement.ColumnInt(0)); EXPECT_EQ(3, statement.ColumnInt(1)); EXPECT_EQ("FLOAT", statement.ColumnString(2)); EXPECT_DOUBLE_EQ(3.1415927, statement.ColumnDouble(3)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(4, statement.ColumnInt(0)); EXPECT_EQ(4, statement.ColumnInt(1)); EXPECT_EQ("TEXT", statement.ColumnString(2)); EXPECT_EQ("This is text", statement.ColumnString(3)); ASSERT_TRUE(statement.Step()); EXPECT_EQ(5, statement.ColumnInt(0)); EXPECT_EQ(5, statement.ColumnInt(1)); EXPECT_EQ("BLOB", statement.ColumnString(2)); std::string blob_text; ASSERT_TRUE(statement.ColumnBlobAsString(3, &blob_text)); EXPECT_EQ("This is a blob", blob_text); EXPECT_FALSE(statement.Step()); } TEST_F(RecoverModuleTest, IntegerEncodings) { ASSERT_TRUE(db_.Execute("CREATE TABLE integers(value)")); const std::vector values = { // Encoded directly in type info. 0, 1, // 8-bit signed. 2, -2, 127, -128, // 16-bit signed. 12345, -12345, 32767, -32768, // 24-bit signed. 1234567, -1234567, 8388607, -8388608, // 32-bit signed. 1234567890, -1234567890, 2147483647, -2147483647, // 48-bit signed. 123456789012345, -123456789012345, 140737488355327, -140737488355327, // 64-bit signed. 1234567890123456789, -1234567890123456789, 9223372036854775807, -9223372036854775807, }; sql::Statement insert( db_.GetUniqueStatement("INSERT INTO integers VALUES(?)")); for (int64_t value : values) { insert.BindInt64(0, value); ASSERT_TRUE(insert.Run()); insert.Reset(/* clear_bound_vars= */ true); } ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_integers " "USING recover(integers, value INTEGER)")); sql::Statement select( db_.GetUniqueStatement("SELECT rowid, value FROM recover_integers")); for (size_t i = 0; i < values.size(); ++i) { ASSERT_TRUE(select.Step()) << "Was attemping to read " << values[i]; EXPECT_EQ(static_cast(i + 1), select.ColumnInt(0)); EXPECT_EQ(values[i], select.ColumnInt64(1)); } EXPECT_FALSE(select.Step()); } TEST_F(RecoverModuleTest, VarintEncodings) { const std::vector values = { // 1-byte varints. 0x00, 0x01, 0x02, 0x7e, 0x7f, // 2-byte varints 0x80, 0x81, 0xff, 0x0100, 0x0101, 0x1234, 0x1ffe, 0x1fff, 0x3ffe, 0x3fff, // 3-byte varints 0x4000, 0x4001, 0x0ffffe, 0x0fffff, 0x123456, 0x1fedcb, 0x1ffffe, 0x1fffff, // 4-byte varints 0x200000, 0x200001, 0x123456, 0xfedcba, 0xfffffe, 0xffffff, 0x01234567, 0x0fedcba9, 0x0ffffffe, 0x0fffffff, // 5-byte varints 0x10000000, 0x10000001, 0x12345678, 0xfedcba98, 0x01'23456789, 0x07'fffffffe, 0x07'ffffffff, // 6-byte varints 0x08'00000000, 0x08'00000001, 0x12'3456789a, 0xfe'dcba9876, 0x0123'456789ab, 0x03ff'fffffffe, 0x03ff'ffffffff, // 7-byte varints 0x0400'00000000, 0x0400'00000001, 0xfedc'ba987654, 0x012345'6789abcd, 0x01ffff'fffffffe, 0x01ffff'ffffffff, // 8-byte varints 0x020000'00000000, 0x020000'00000001, 0x0fedcb'a9876543, 0x123456'789abcde, 0xfedcba'98765432, 0xffffff'fffffffe, 0xffffff'ffffffff, // 9-byte positive varints 0x01000000'00000000, 0x01000000'00000001, 0x12345678'9abcdef0, 0x7fedcba9'87654321, 0x7fffffff'fffffffe, 0x7fffffff'ffffffff, // 9-byte negative varints -0x01, -0x02, -0x7e, -0x7f, -0x80, -0x81, -0x12345678'9abcdef0, -0x7fedcba9'87654321, -0x7fffffff'ffffffff, -0x7fffffff'ffffffff - 1, // -0x80000000'00000000 is not a valid literal }; ASSERT_TRUE(db_.Execute("CREATE TABLE varints(value INTEGER PRIMARY KEY)")); ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_varints " "USING recover(varints, value ROWID)")); for (int64_t value : values) { sql::Statement insert( db_.GetUniqueStatement("INSERT INTO varints VALUES(?)")); insert.BindInt64(0, value); ASSERT_TRUE(insert.Run()); sql::Statement select( db_.GetUniqueStatement("SELECT rowid, value FROM recover_varints")); ASSERT_TRUE(select.Step()) << "Was attemping to read " << value; EXPECT_EQ(value, select.ColumnInt64(0)); EXPECT_EQ(value, select.ColumnInt64(1)); EXPECT_FALSE(select.Step()); ASSERT_TRUE(db_.Execute("DELETE FROM varints")); } } TEST_F(RecoverModuleTest, TextEncodings) { ASSERT_TRUE(db_.Execute("CREATE TABLE encodings(t TEXT)")); const std::vector values = { "", "a", "ö", "Mjollnir", "Mjölnir", "Mjǫlnir", "Mjölner", "Mjølner", "ハンマー", }; sql::Statement insert( db_.GetUniqueStatement("INSERT INTO encodings VALUES(?)")); for (const std::string& value : values) { insert.BindString(0, value); ASSERT_TRUE(insert.Run()); insert.Reset(/* clear_bound_vars= */ true); } ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_encodings " "USING recover(encodings, t TEXT)")); sql::Statement select( db_.GetUniqueStatement("SELECT rowid, t FROM recover_encodings")); for (size_t i = 0; i < values.size(); ++i) { ASSERT_TRUE(select.Step()); EXPECT_EQ(static_cast(i + 1), select.ColumnInt(0)); EXPECT_EQ(values[i], select.ColumnString(1)); } EXPECT_FALSE(select.Step()); } TEST_F(RecoverModuleTest, BlobEncodings) { ASSERT_TRUE(db_.Execute("CREATE TABLE blob_encodings(t BLOB)")); const std::vector> values = { {}, {0x00}, {0x01}, {0x42}, {0xff}, {0x00, 0x00}, {0x00, 0x01}, {0x00, 0xff}, {0x42, 0x43, 0x44, 0x45, 0x46}, }; sql::Statement insert( db_.GetUniqueStatement("INSERT INTO blob_encodings VALUES(?)")); for (const std::vector& value : values) { insert.BindBlob(0, value); ASSERT_TRUE(insert.Run()); insert.Reset(/* clear_bound_vars= */ true); } ASSERT_TRUE( db_.Execute("CREATE VIRTUAL TABLE temp.recover_blob_encodings " "USING recover(blob_encodings, t BLOB)")); sql::Statement select( db_.GetUniqueStatement("SELECT rowid, t FROM recover_blob_encodings")); for (size_t i = 0; i < values.size(); ++i) { ASSERT_TRUE(select.Step()); EXPECT_EQ(static_cast(i + 1), select.ColumnInt(0)); std::vector column_value; EXPECT_TRUE(select.ColumnBlobAsVector(1, &column_value)); EXPECT_EQ(values[i], column_value); } EXPECT_FALSE(select.Step()); } namespace { std::string RandomString(int size) { std::mt19937 rng; std::uniform_int_distribution random_char(32, 127); std::string large_value; large_value.reserve(size); for (int i = 0; i < size; ++i) large_value.push_back(random_char(rng)); return large_value; } void CheckLargeValueRecovery(sql::Database* db, int value_size) { const std::string large_value = RandomString(value_size); ASSERT_TRUE(db->Execute("CREATE TABLE overflow(t TEXT)")); sql::Statement insert( db->GetUniqueStatement("INSERT INTO overflow VALUES(?)")); insert.BindString(0, large_value); ASSERT_TRUE(insert.Run()); ASSERT_TRUE(db->Execute("VACUUM")); ASSERT_TRUE( db->Execute("CREATE VIRTUAL TABLE temp.recover_overflow " "USING recover(overflow, t TEXT)")); sql::Statement select( db->GetUniqueStatement("SELECT rowid, t FROM recover_overflow")); ASSERT_TRUE(select.Step()); EXPECT_EQ(1, select.ColumnInt(0)); EXPECT_EQ(large_value, select.ColumnString(1)); } bool HasEnabledAutoVacuum(sql::Database* db) { sql::Statement pragma(db->GetUniqueStatement("PRAGMA auto_vacuum")); EXPECT_TRUE(pragma.Step()); return pragma.ColumnInt(0) != 0; } // The overhead in the table page is: // * 35 bytes - the cell size cutoff that causes a cell's payload to overflow // * 1 byte - record header size // * 2-3 bytes - type ID for the text column // - texts of 58-8185 bytes use 2 bytes // - texts of 8186-1048569 bytes use 3 bytes // // The overhead below assumes a 2-byte string type ID. constexpr int kRecordOverhead = 38; // Each overflow page uses 4 bytes to store the pointer to the next page. constexpr int kOverflowOverhead = 4; } // namespace TEST_F(RecoverModuleTest, ValueWithoutOverflow) { CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead); int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0; ASSERT_EQ(2 + auto_vacuum_pages, sql::test::GetPageCount(&db_)) << "Database should have a root page and a leaf page"; } TEST_F(RecoverModuleTest, ValueWithOneByteOverflow) { CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead + 1); int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0; ASSERT_EQ(3 + auto_vacuum_pages, sql::test::GetPageCount(&db_)) << "Database should have a root page, a leaf page, and 1 overflow page"; } TEST_F(RecoverModuleTest, ValueWithOneOverflowPage) { CheckLargeValueRecovery( &db_, db_.page_size() - kRecordOverhead + db_.page_size() / 2); int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0; ASSERT_EQ(3 + auto_vacuum_pages, sql::test::GetPageCount(&db_)) << "Database should have a root page, a leaf page, and 1 overflow page"; } TEST_F(RecoverModuleTest, ValueWithOneFullOverflowPage) { CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead + db_.page_size() - kOverflowOverhead); int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0; ASSERT_EQ(3 + auto_vacuum_pages, sql::test::GetPageCount(&db_)) << "Database should have a root page, a leaf page, and 1 overflow page"; } TEST_F(RecoverModuleTest, ValueWithOneByteSecondOverflowPage) { CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead + db_.page_size() - kOverflowOverhead + 1); int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0; ASSERT_EQ(4 + auto_vacuum_pages, sql::test::GetPageCount(&db_)) << "Database should have a root page, a leaf page, and 2 overflow pages"; } TEST_F(RecoverModuleTest, ValueWithTwoOverflowPages) { CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead + db_.page_size() - kOverflowOverhead + db_.page_size() / 2); int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0; ASSERT_EQ(4 + auto_vacuum_pages, sql::test::GetPageCount(&db_)) << "Database should have a root page, a leaf page, and 2 overflow pages"; } TEST_F(RecoverModuleTest, ValueWithTwoFullOverflowPages) { // This value is large enough that the varint encoding of its type ID takes up // 3 bytes, instead of 2. CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead + (db_.page_size() - kOverflowOverhead) * 2 - 1); int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0; ASSERT_EQ(4 + auto_vacuum_pages, sql::test::GetPageCount(&db_)) << "Database should have a root page, a leaf page, and 2 overflow pages"; } } // namespace recover } // namespace sql