#include "catch.hpp" #include "duckdb/common/value_operations/value_operations.hpp" #include "test_helpers.hpp" #include #include #include using namespace duckdb; using namespace std; static constexpr int CONCURRENT_DELETE_THREAD_COUNT = 10; static constexpr int CONCURRENT_DELETE_INSERT_ELEMENTS = 100; TEST_CASE("Single thread delete", "[interquery][.]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db); duckdb::vector> connections; // enable detailed profiling con.Query("PRAGMA enable_profiling"); auto detailed_profiling_output = TestCreatePath("detailed_profiling_output"); con.Query("PRAGMA profiling_output='" + detailed_profiling_output + "'"); con.Query("PRAGMA profiling_mode = detailed"); // initialize the database con.Query("CREATE TABLE integers(i INTEGER);"); int sum = 0; for (size_t i = 0; i < CONCURRENT_DELETE_INSERT_ELEMENTS; i++) { for (size_t j = 0; j < 10; j++) { con.Query("INSERT INTO integers VALUES (" + to_string(j + 1) + ");"); sum += j + 1; } } // check the sum result = con.Query("SELECT SUM(i) FROM integers"); REQUIRE(CHECK_COLUMN(result, 0, {sum})); // simple delete, we should delete CONCURRENT_DELETE_INSERT_ELEMENTS elements result = con.Query("DELETE FROM integers WHERE i=2"); REQUIRE(CHECK_COLUMN(result, 0, {CONCURRENT_DELETE_INSERT_ELEMENTS})); // check sum again result = con.Query("SELECT SUM(i) FROM integers"); REQUIRE(CHECK_COLUMN(result, 0, {sum - 2 * CONCURRENT_DELETE_INSERT_ELEMENTS})); } TEST_CASE("Sequential delete", "[interquery][.]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db); duckdb::vector> connections; Value count; // enable detailed profiling con.Query("PRAGMA enable_profiling"); auto detailed_profiling_output = TestCreatePath("detailed_profiling_output"); con.Query("PRAGMA profiling_output='" + detailed_profiling_output + "'"); con.Query("PRAGMA profiling_mode = detailed"); // initialize the database con.Query("CREATE TABLE integers(i INTEGER);"); int sum = 0; for (size_t i = 0; i < CONCURRENT_DELETE_INSERT_ELEMENTS; i++) { for (size_t j = 0; j < 10; j++) { con.Query("INSERT INTO integers VALUES (" + to_string(j + 1) + ");"); sum += j + 1; } } for (size_t i = 0; i < CONCURRENT_DELETE_THREAD_COUNT; i++) { connections.push_back(make_uniq(db)); connections[i]->Query("BEGIN TRANSACTION;"); } for (size_t i = 0; i < CONCURRENT_DELETE_THREAD_COUNT; i++) { // check the current count result = connections[i]->Query("SELECT SUM(i) FROM integers"); REQUIRE_NO_FAIL(*result); count = result->GetValue(0, 0); REQUIRE(count == sum); // delete the elements for this thread REQUIRE_NO_FAIL(connections[i]->Query("DELETE FROM integers WHERE i=" + to_string(i + 1))); // check the updated count result = connections[i]->Query("SELECT SUM(i) FROM integers"); REQUIRE_NO_FAIL(*result); count = result->GetValue(0, 0); REQUIRE(count == sum - (i + 1) * CONCURRENT_DELETE_INSERT_ELEMENTS); } // check the count on the original connection result = con.Query("SELECT SUM(i) FROM integers"); REQUIRE_NO_FAIL(*result); count = result->GetValue(0, 0); REQUIRE(count == sum); // commit everything for (size_t i = 0; i < CONCURRENT_DELETE_THREAD_COUNT; i++) { connections[i]->Query("COMMIT;"); } // check that the count is 0 now result = con.Query("SELECT COUNT(i) FROM integers"); REQUIRE_NO_FAIL(*result); count = result->GetValue(0, 0); REQUIRE(count == 0); } TEST_CASE("Rollback delete", "[interquery][.]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db); duckdb::vector> connections; // enable detailed profiling con.Query("PRAGMA enable_profiling"); auto detailed_profiling_output = TestCreatePath("detailed_profiling_output"); con.Query("PRAGMA profiling_output='" + detailed_profiling_output + "'"); con.Query("PRAGMA profiling_mode = detailed"); // initialize the database con.Query("CREATE TABLE integers(i INTEGER);"); int sum = 0; for (size_t i = 0; i < CONCURRENT_DELETE_INSERT_ELEMENTS; i++) { for (size_t j = 0; j < 10; j++) { con.Query("INSERT INTO integers VALUES (" + to_string(j + 1) + ");"); sum += j + 1; } } // begin transaction REQUIRE_NO_FAIL(con.Query("BEGIN TRANSACTION")); // check the sum result = con.Query("SELECT SUM(i) FROM integers"); REQUIRE(CHECK_COLUMN(result, 0, {sum})); // simple delete result = con.Query("DELETE FROM integers WHERE i=2"); REQUIRE(CHECK_COLUMN(result, 0, {100})); // check sum again result = con.Query("SELECT SUM(i) FROM integers"); REQUIRE(CHECK_COLUMN(result, 0, {sum - 2 * CONCURRENT_DELETE_INSERT_ELEMENTS})); // rollback transaction REQUIRE_NO_FAIL(con.Query("ROLLBACK")); // check the sum again result = con.Query("SELECT SUM(i) FROM integers"); REQUIRE(CHECK_COLUMN(result, 0, {sum})); } static volatile std::atomic delete_finished_threads; static void delete_elements(DuckDB *db, bool *correct, size_t threadnr) { correct[threadnr] = true; Connection con(*db); // initial count con.Query("BEGIN TRANSACTION;"); auto result = con.Query("SELECT COUNT(*) FROM integers"); Value count = result->GetValue(0, 0); auto start_count = count.GetValue(); for (size_t i = 0; i < CONCURRENT_DELETE_INSERT_ELEMENTS; i++) { // count should decrease by one for every delete we do auto element = CONCURRENT_DELETE_INSERT_ELEMENTS * threadnr + i; if (con.Query("DELETE FROM integers WHERE i=" + to_string(element))->HasError()) { correct[threadnr] = false; } result = con.Query("SELECT COUNT(*) FROM integers"); if (result->HasError()) { correct[threadnr] = false; } else { Value new_count = result->GetValue(0, 0); if (new_count != start_count - (i + 1)) { correct[threadnr] = false; } count = new_count; } } delete_finished_threads++; while (delete_finished_threads != CONCURRENT_DELETE_THREAD_COUNT) ; con.Query("COMMIT;"); } TEST_CASE("Concurrent delete", "[interquery][.]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db); // enable detailed profiling con.Query("PRAGMA enable_profiling"); auto detailed_profiling_output = TestCreatePath("detailed_profiling_output"); con.Query("PRAGMA profiling_output='" + detailed_profiling_output + "'"); con.Query("PRAGMA profiling_mode = detailed"); // initialize the database REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER);")); for (size_t i = 0; i < CONCURRENT_DELETE_INSERT_ELEMENTS; i++) { for (size_t j = 0; j < CONCURRENT_DELETE_THREAD_COUNT; j++) { auto element = CONCURRENT_DELETE_INSERT_ELEMENTS * j + i; con.Query("INSERT INTO integers VALUES (" + to_string(element) + ");"); } } delete_finished_threads = 0; bool correct[CONCURRENT_DELETE_THREAD_COUNT]; thread threads[CONCURRENT_DELETE_THREAD_COUNT]; for (size_t i = 0; i < CONCURRENT_DELETE_THREAD_COUNT; i++) { threads[i] = thread(delete_elements, &db, correct, i); } for (size_t i = 0; i < CONCURRENT_DELETE_THREAD_COUNT; i++) { threads[i].join(); REQUIRE(correct[i]); } // check that the count is 0 now result = con.Query("SELECT COUNT(i) FROM integers"); REQUIRE_NO_FAIL(*result); auto count = result->GetValue(0, 0); REQUIRE(count == 0); }