// Copyright (c) 2011 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/transaction.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "sql/database.h" #include "sql/statement.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/sqlite/sqlite3.h" namespace sql { namespace { class SQLTransactionTest : public testing::Test { public: ~SQLTransactionTest() override = default; void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); ASSERT_TRUE( db_.Open(temp_dir_.GetPath().AppendASCII("transaction_test.sqlite"))); ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a, b)")); } // Returns the number of rows in table "foo". int CountFoo() { Statement count(db_.GetUniqueStatement("SELECT count(*) FROM foo")); count.Step(); return count.ColumnInt(0); } protected: base::ScopedTempDir temp_dir_; Database db_; }; TEST_F(SQLTransactionTest, Commit) { { Transaction transaction(&db_); EXPECT_FALSE(db_.HasActiveTransactions()); EXPECT_FALSE(transaction.IsActiveForTesting()); ASSERT_TRUE(transaction.Begin()); EXPECT_TRUE(db_.HasActiveTransactions()); EXPECT_TRUE(transaction.IsActiveForTesting()); ASSERT_TRUE(db_.Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); ASSERT_EQ(1, CountFoo()) << "INSERT did not work as intended"; transaction.Commit(); EXPECT_FALSE(db_.HasActiveTransactions()); EXPECT_FALSE(transaction.IsActiveForTesting()); } EXPECT_FALSE(db_.HasActiveTransactions()); EXPECT_EQ(1, CountFoo()) << "Transaction changes not committed"; } TEST_F(SQLTransactionTest, RollbackOnDestruction) { EXPECT_FALSE(db_.HasActiveTransactions()); { Transaction transaction(&db_); EXPECT_FALSE(db_.HasActiveTransactions()); EXPECT_FALSE(transaction.IsActiveForTesting()); ASSERT_TRUE(transaction.Begin()); EXPECT_TRUE(db_.HasActiveTransactions()); EXPECT_TRUE(transaction.IsActiveForTesting()); ASSERT_TRUE(db_.Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); ASSERT_EQ(1, CountFoo()) << "INSERT did not work as intended"; } EXPECT_FALSE(db_.HasActiveTransactions()); EXPECT_EQ(0, CountFoo()) << "Transaction changes not rolled back"; } TEST_F(SQLTransactionTest, ExplicitRollback) { EXPECT_FALSE(db_.HasActiveTransactions()); { Transaction transaction(&db_); EXPECT_FALSE(db_.HasActiveTransactions()); EXPECT_FALSE(transaction.IsActiveForTesting()); ASSERT_TRUE(transaction.Begin()); EXPECT_TRUE(db_.HasActiveTransactions()); EXPECT_TRUE(transaction.IsActiveForTesting()); ASSERT_TRUE(db_.Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); ASSERT_EQ(1, CountFoo()) << "INSERT did not work as intended"; transaction.Rollback(); EXPECT_FALSE(db_.HasActiveTransactions()); EXPECT_FALSE(transaction.IsActiveForTesting()); EXPECT_EQ(0, CountFoo()) << "Transaction changes not rolled back"; } EXPECT_FALSE(db_.HasActiveTransactions()); EXPECT_EQ(0, CountFoo()) << "Transaction changes not rolled back"; } // Rolling back any part of a transaction should roll back all of them. TEST_F(SQLTransactionTest, NestedRollback) { EXPECT_FALSE(db_.HasActiveTransactions()); EXPECT_EQ(0, db_.transaction_nesting()); // Outermost transaction. { Transaction outer_txn(&db_); EXPECT_FALSE(db_.HasActiveTransactions()); EXPECT_EQ(0, db_.transaction_nesting()); ASSERT_TRUE(outer_txn.Begin()); EXPECT_TRUE(db_.HasActiveTransactions()); EXPECT_EQ(1, db_.transaction_nesting()); // First inner transaction is committed. { Transaction committed_inner_txn(&db_); EXPECT_TRUE(db_.HasActiveTransactions()); EXPECT_EQ(1, db_.transaction_nesting()); ASSERT_TRUE(committed_inner_txn.Begin()); EXPECT_TRUE(db_.HasActiveTransactions()); EXPECT_EQ(2, db_.transaction_nesting()); ASSERT_TRUE(db_.Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); ASSERT_EQ(1, CountFoo()) << "INSERT did not work as intended"; committed_inner_txn.Commit(); EXPECT_TRUE(db_.HasActiveTransactions()); EXPECT_EQ(1, db_.transaction_nesting()); } EXPECT_TRUE(db_.HasActiveTransactions()); EXPECT_EQ(1, db_.transaction_nesting()); EXPECT_EQ(1, CountFoo()) << "First inner transaction did not commit"; // Second inner transaction is rolled back. { Transaction rolled_back_inner_txn(&db_); EXPECT_TRUE(db_.HasActiveTransactions()); EXPECT_EQ(1, db_.transaction_nesting()); ASSERT_TRUE(rolled_back_inner_txn.Begin()); EXPECT_TRUE(db_.HasActiveTransactions()); EXPECT_EQ(2, db_.transaction_nesting()); ASSERT_TRUE(db_.Execute("INSERT INTO foo (a, b) VALUES (2, 3)")); ASSERT_EQ(2, CountFoo()) << "INSERT did not work as intended"; rolled_back_inner_txn.Rollback(); EXPECT_TRUE(db_.HasActiveTransactions()); EXPECT_EQ(1, db_.transaction_nesting()); EXPECT_EQ(2, CountFoo()) << "Nested transaction rollback deferred to top-level transaction"; } EXPECT_TRUE(db_.HasActiveTransactions()); EXPECT_EQ(1, db_.transaction_nesting()); EXPECT_EQ(2, CountFoo()) << "Nested transaction rollback deferred to top-level transaction"; // Third inner transaction fails in Begin(), because a nested transaction // has already been rolled back. { Transaction failed_inner_txn(&db_); EXPECT_TRUE(db_.HasActiveTransactions()); EXPECT_EQ(1, db_.transaction_nesting()); EXPECT_FALSE(failed_inner_txn.Begin()); EXPECT_TRUE(db_.HasActiveTransactions()); EXPECT_EQ(1, db_.transaction_nesting()); } } EXPECT_FALSE(db_.HasActiveTransactions()); EXPECT_EQ(0, db_.transaction_nesting()); EXPECT_EQ(0, CountFoo()); } } // namespace } // namespace sql