From da2a7cfc58f3df6101e035734549b234f76d3b88 Mon Sep 17 00:00:00 2001 From: NejcZdovc Date: Fri, 3 Jan 2020 11:31:06 +0100 Subject: [PATCH] Fixed contribution queue not being deleted Resolves https://github.com/brave/brave-browser/issues/7579 --- .../database/database_contribution_queue.cc | 51 ++++++++++++++-- .../database/database_contribution_queue.h | 2 + .../database_contribution_queue_publishers.cc | 56 ++++++++++++++++++ .../database_contribution_queue_publishers.h | 6 ++ .../publisher_info_database_unittest.cc | 44 +++++++++++++- .../database/contribution_queue_key_corrupted | Bin 0 -> 139264 bytes .../database/contribution_queue_key_ok | Bin 0 -> 139264 bytes 7 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 test/data/rewards-data/database/contribution_queue_key_corrupted create mode 100644 test/data/rewards-data/database/contribution_queue_key_ok diff --git a/components/brave_rewards/browser/database/database_contribution_queue.cc b/components/brave_rewards/browser/database/database_contribution_queue.cc index 3448b8d014a4..b7f81b8cc1fe 100644 --- a/components/brave_rewards/browser/database/database_contribution_queue.cc +++ b/components/brave_rewards/browser/database/database_contribution_queue.cc @@ -49,6 +49,14 @@ bool DatabaseContributionQueue::Init(sql::Database* db) { return false; } + // We need to clear queue if user has corrupted foreign key + // more info: https://github.com/brave/brave-browser/issues/7579 + if (publishers_->HasCorruptedForeignKey(db)) { + if (!DeleteAllRecords(db)) { + return false; + } + } + return transaction.Commit(); } @@ -148,8 +156,13 @@ ledger::ContributionQueuePtr DatabaseContributionQueue::GetFirstRecord( bool DatabaseContributionQueue::DeleteRecord( sql::Database* db, const uint64_t id) { - if (!db->Execute("PRAGMA foreign_keys=1;")) { - LOG(ERROR) << "Error: deleting record for contribution queue with id" << id; + DCHECK(db); + if (id == 0 || !db) { + return false; + } + + sql::Transaction transaction(db); + if (!transaction.Begin()) { return false; } @@ -163,11 +176,41 @@ bool DatabaseContributionQueue::DeleteRecord( bool success = statement.Run(); - if (!db->Execute("PRAGMA foreign_keys=0;")) { + if (!success) { + return false; + } + + if (!publishers_->DeleteRecordsByQueueId(db, id)) { + return false; + } + + return transaction.Commit(); +} + +bool DatabaseContributionQueue::DeleteAllRecords(sql::Database* db) { + DCHECK(db); + if (!db) { return false; } - return success; + sql::Transaction transaction(db); + if (!transaction.Begin()) { + return false; + } + + const std::string query = base::StringPrintf("DELETE FROM %s", table_name_); + sql::Statement statement(db->GetUniqueStatement(query.c_str())); + bool success = statement.Run(); + + if (!success) { + return false; + } + + if (!publishers_->DeleteAllRecords(db)) { + return false; + } + + return transaction.Commit(); } } // namespace brave_rewards diff --git a/components/brave_rewards/browser/database/database_contribution_queue.h b/components/brave_rewards/browser/database/database_contribution_queue.h index 06f87636005e..449d32c50e29 100644 --- a/components/brave_rewards/browser/database/database_contribution_queue.h +++ b/components/brave_rewards/browser/database/database_contribution_queue.h @@ -32,6 +32,8 @@ class DatabaseContributionQueue: public DatabaseTable { bool DeleteRecord(sql::Database* db, const uint64_t id); + bool DeleteAllRecords(sql::Database* db); + private: std::string GetIdColumnName(); diff --git a/components/brave_rewards/browser/database/database_contribution_queue_publishers.cc b/components/brave_rewards/browser/database/database_contribution_queue_publishers.cc index b26709d22917..9e8d2b3d672d 100644 --- a/components/brave_rewards/browser/database/database_contribution_queue_publishers.cc +++ b/components/brave_rewards/browser/database/database_contribution_queue_publishers.cc @@ -13,6 +13,10 @@ #include "sql/statement.h" #include "sql/transaction.h" +namespace { + const char kCorruptedForeignKey[] = "publisher_info_old"; +} // namespace + namespace brave_rewards { DatabaseContributionQueuePublishers::DatabaseContributionQueuePublishers( @@ -121,4 +125,56 @@ DatabaseContributionQueuePublishers::GetRecords( return list; } +bool DatabaseContributionQueuePublishers::DeleteRecordsByQueueId( + sql::Database* db, + const uint64_t queue_id) { + DCHECK(db); + if (queue_id == 0 || !db) { + return false; + } + + const std::string query = base::StringPrintf( + "DELETE FROM %s WHERE %s_id = ?", + table_name_, + parent_table_name_); + + sql::Statement statement(db->GetUniqueStatement(query.c_str())); + statement.BindInt64(0, queue_id); + + return statement.Run(); +} + +bool DatabaseContributionQueuePublishers::HasCorruptedForeignKey( + sql::Database* db) { + DCHECK(db); + if (!db) { + return false; + } + + const std::string query = base::StringPrintf( + "PRAGMA foreign_key_list(%s)", + table_name_); + + sql::Statement statement(db->GetUniqueStatement(query.c_str())); + + while (statement.Step()) { + if (statement.ColumnString(2) == kCorruptedForeignKey) { + return true; + } + } + + return false; +} + +bool DatabaseContributionQueuePublishers::DeleteAllRecords(sql::Database* db) { + DCHECK(db); + if (!db) { + return false; + } + + const std::string query = base::StringPrintf("DELETE FROM %s", table_name_); + sql::Statement statement(db->GetUniqueStatement(query.c_str())); + return statement.Run(); +} + } // namespace brave_rewards diff --git a/components/brave_rewards/browser/database/database_contribution_queue_publishers.h b/components/brave_rewards/browser/database/database_contribution_queue_publishers.h index 0e7f2b9f5eb5..47a55dca4f9b 100644 --- a/components/brave_rewards/browser/database/database_contribution_queue_publishers.h +++ b/components/brave_rewards/browser/database/database_contribution_queue_publishers.h @@ -33,6 +33,12 @@ class DatabaseContributionQueuePublishers: public DatabaseTable { sql::Database* db, const uint64_t queue_id); + bool DeleteRecordsByQueueId(sql::Database* db, const uint64_t queue_id); + + bool HasCorruptedForeignKey(sql::Database* db); + + bool DeleteAllRecords(sql::Database* db); + private: const char* table_name_ = "contribution_queue_publishers"; const int minimum_version_ = 9; diff --git a/components/brave_rewards/browser/database/publisher_info_database_unittest.cc b/components/brave_rewards/browser/database/publisher_info_database_unittest.cc index bb6ca215f5e5..93c96131526d 100644 --- a/components/brave_rewards/browser/database/publisher_info_database_unittest.cc +++ b/components/brave_rewards/browser/database/publisher_info_database_unittest.cc @@ -61,6 +61,16 @@ class PublisherInfoDatabaseTest : public ::testing::Test { int end_version) { const std::string file_name = "publisher_info_db_v" + std::to_string(start_version); + + LoadDatabaseFile(temp_dir, db_file, file_name, "migration", end_version); + } + + void LoadDatabaseFile( + base::ScopedTempDir* temp_dir, + base::FilePath* db_file, + const std::string& file_name, + const std::string& folder, + int end_version) { ASSERT_TRUE(temp_dir->CreateUniqueTempDir()); *db_file = temp_dir->GetPath().AppendASCII(file_name); @@ -69,7 +79,7 @@ class PublisherInfoDatabaseTest : public ::testing::Test { ASSERT_TRUE(base::PathService::Get(brave::DIR_TEST_DATA, &path)); path = path.AppendASCII("rewards-data"); ASSERT_TRUE(base::PathExists(path)); - path = path.AppendASCII("migration"); + path = path.AppendASCII(folder); ASSERT_TRUE(base::PathExists(path)); path = path.AppendASCII(file_name); ASSERT_TRUE(base::PathExists(path)); @@ -1541,4 +1551,36 @@ TEST_F(PublisherInfoDatabaseTest, RemoveAllPendingContributions) { EXPECT_EQ(CountTableRows("pending_contribution"), 0); } +TEST_F(PublisherInfoDatabaseTest, + ContributionQueueKeyCorrupted) { + base::ScopedTempDir temp_dir; + base::FilePath db_file; + LoadDatabaseFile( + &temp_dir, + &db_file, + "contribution_queue_key_corrupted", + "database", + 9); + EXPECT_TRUE(publisher_info_database_->Init()); + + EXPECT_EQ(CountTableRows("contribution_queue"), 0); + EXPECT_EQ(CountTableRows("contribution_queue_publishers"), 0); +} + +TEST_F(PublisherInfoDatabaseTest, + ContributionQueueKeyOk) { + base::ScopedTempDir temp_dir; + base::FilePath db_file; + LoadDatabaseFile( + &temp_dir, + &db_file, + "contribution_queue_key_ok", + "database", + 9); + EXPECT_TRUE(publisher_info_database_->Init()); + + EXPECT_EQ(CountTableRows("contribution_queue"), 1); + EXPECT_EQ(CountTableRows("contribution_queue_publishers"), 1); +} + } // namespace brave_rewards diff --git a/test/data/rewards-data/database/contribution_queue_key_corrupted b/test/data/rewards-data/database/contribution_queue_key_corrupted new file mode 100644 index 0000000000000000000000000000000000000000..39b634c12767bbec59c03fffa8e8aefc80123d11 GIT binary patch literal 139264 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 S<6K#n_m%02Rv%!_|NkF|pkMp| literal 0 HcmV?d00001 diff --git a/test/data/rewards-data/database/contribution_queue_key_ok b/test/data/rewards-data/database/contribution_queue_key_ok new file mode 100644 index 0000000000000000000000000000000000000000..9815d5fb2eeb1662164b0f704ee27816724d4759 GIT binary patch literal 139264 zcmeI*&u`n%1r~EzpuqkKz4cZsu-H?J_OQ3XZud|WDB8}YKreldq$&QC z>?G*M##eD{i|_IAz0do>N9l>)e6Xe%GT&0Qj%4t6VwYnK6Z8-KP9lf7B+GElPbuHJ^)`+~{kX(%ll9YF9E8wJYX=+b%!(&Lqoa zv&_+gA=TS*uU~H~`nIfzO1GtYpG_4iLcS{S)%^Qw0-x~)X80Q^-m|fp;n&Kg)$ipi zg%9!-zErOArP|usO&U#p+-UckGKo?wRfSby5X`R#_w%*2DnD;V>`EOu!&ik*$j
-ow<{v+Wbf5Tm_W+qE>Q7aW6)C7MJpWiHG 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