#include "catch.hpp" #include "test_helpers.hpp" using namespace duckdb; using namespace std; static void CreateSimpleTable(Connection &con) { REQUIRE_NO_FAIL(con.Query("CREATE TABLE a (i TINYINT)")); REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (11), (12), (13)")); } static void ModifySimpleTable(Connection &con) { REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (14)")); REQUIRE_NO_FAIL(con.Query("DELETE FROM a where i=12")); } static void CheckSimpleQuery(Connection &con) { auto statements = con.ExtractStatements("SELECT COUNT(*) FROM a WHERE i=12"); REQUIRE(statements.size() == 1); duckdb::vector values = {Value(12)}; auto pending_result = con.PendingQuery("SELECT COUNT(*) FROM a WHERE i=?", values, true); if (pending_result->HasError()) { printf("%s\n", pending_result->GetError().c_str()); } REQUIRE(!pending_result->HasError()); auto result = pending_result->Execute(); REQUIRE(CHECK_COLUMN(result, 0, {1})); } static void CheckCatalogErrorQuery(Connection &con) { duckdb::vector values = {Value(12)}; auto pending_result = con.PendingQuery("SELECT COUNT(*) FROM b WHERE i=?", values, true); REQUIRE((pending_result->HasError() && pending_result->GetErrorType() == ExceptionType::CATALOG)); } static void CheckConversionErrorQuery(Connection &con) { // Check query with invalid prepared value duckdb::vector values = {Value("fawakaaniffoo")}; auto pending_result = con.PendingQuery("SELECT COUNT(*) FROM a WHERE i=?", values, true); REQUIRE(!pending_result->HasError()); auto result = pending_result->Execute(); REQUIRE((result->HasError() && result->GetErrorType() == ExceptionType::CONVERSION)); } static void CheckSimpleQueryAfterModification(Connection &con) { duckdb::vector values = {Value(14)}; auto pending_result = con.PendingQuery("SELECT COUNT(*) FROM a WHERE i=?", values, true); REQUIRE(!pending_result->HasError()); auto result = pending_result->Execute(); REQUIRE(CHECK_COLUMN(result, 0, {1})); } TEST_CASE("Pending Query with Parameters", "[api]") { DuckDB db(nullptr); Connection con(db); con.EnableQueryVerification(); CreateSimpleTable(con); CheckSimpleQuery(con); CheckSimpleQuery(con); } TEST_CASE("Pending Query with Parameters Catalog Error", "[api]") { DuckDB db(nullptr); Connection con(db); con.EnableQueryVerification(); CreateSimpleTable(con); CheckCatalogErrorQuery(con); // Verify things are still sane CheckSimpleQuery(con); } TEST_CASE("Pending Query with Parameters Type Conversion Error", "[api]") { DuckDB db(nullptr); Connection con(db); con.EnableQueryVerification(); CreateSimpleTable(con); CheckConversionErrorQuery(con); // Verify things are still sane CheckSimpleQuery(con); } TEST_CASE("Pending Query with Parameters with transactions", "[api]") { DuckDB db(nullptr); Connection con1(db); Connection con2(db); duckdb::vector empty_values = {}; con1.EnableQueryVerification(); CreateSimpleTable(con1); // CheckConversionErrorQuery(con1); // Begin a transaction in the PrepareAndExecute auto pending_result1 = con1.PendingQuery("BEGIN TRANSACTION", empty_values, true); if (pending_result1->HasError()) { printf("%s\n", pending_result1->GetError().c_str()); } REQUIRE(!pending_result1->HasError()); auto result1 = pending_result1->Execute(); REQUIRE(!result1->HasError()); CheckSimpleQuery(con1); // Modify table on other connection, leaving transaction open con2.BeginTransaction(); ModifySimpleTable(con2); CheckSimpleQueryAfterModification(con2); // con1 sees nothing: both transactions are open CheckSimpleQuery(con1); con2.Commit(); // con1 still sees nothing: its transaction was started before con2's CheckSimpleQuery(con1); // con 1 commits auto pending_result2 = con1.PendingQuery("COMMIT", empty_values, true); auto result2 = pending_result2->Execute(); REQUIRE(!result2->HasError()); // now con1 should see changes from con2 CheckSimpleQueryAfterModification(con1); CheckSimpleQueryAfterModification(con2); }