blob: 2124295c567e0b02ebd65573e170039612a2c69b [file] [log] [blame]
// 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 <random>
#include <string>
#include <tuple>
#include <vector>
#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_MISUSE);
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_MISUSE);
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_MISUSE);
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_MISUSE);
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_MISUSE);
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_MISUSE);
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_MISUSE);
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_MISUSE);
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_MISUSE);
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<const char*, const char*, bool>> {
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<int64_t> 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<int>(i + 1), select.ColumnInt(0));
EXPECT_EQ(values[i], select.ColumnInt64(1));
}
EXPECT_FALSE(select.Step());
}
TEST_F(RecoverModuleTest, VarintEncodings) {
const std::vector<int64_t> 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<std::string> values = {
"", "a", u8"ö", u8"Mjollnir", u8"Mjölnir",
u8"Mjǫlnir", u8"Mjölner", u8"Mjølner", u8"ハンマー",
};
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<int>(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<std::vector<uint8_t>> 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<uint8_t>& 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<int>(i + 1), select.ColumnInt(0));
std::vector<uint8_t> 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<int> 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