#include "catch.hpp" #include "test_helpers.hpp" using namespace duckdb; using namespace std; TEST_CASE("Test prepared statements API", "[api]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db); con.EnableQueryVerification(); // prepare no statements REQUIRE_FAIL(con.Prepare("")); // PrepareAndExecute with no values duckdb::vector values; REQUIRE_FAIL(con.PendingQuery("", values, false)); REQUIRE_NO_FAIL(con.Query("CREATE TABLE a (i TINYINT)")); REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (11), (12), (13)")); REQUIRE_NO_FAIL(con.Query("CREATE TABLE strings(s VARCHAR)")); REQUIRE_NO_FAIL(con.Query("INSERT INTO strings VALUES (NULL), ('test')")); // query using a prepared statement // integer: result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1", 12); REQUIRE(CHECK_COLUMN(result, 0, {1})); // strings: result = con.Query("SELECT COUNT(*) FROM strings WHERE s=$1", "test"); REQUIRE(CHECK_COLUMN(result, 0, {1})); // multiple parameters result = con.Query("SELECT COUNT(*) FROM a WHERE i>$1 AND i<$2", 10, 13); REQUIRE(CHECK_COLUMN(result, 0, {2})); // test various integer types result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1", (int8_t)12); REQUIRE(CHECK_COLUMN(result, 0, {1})); result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1", (int16_t)12); REQUIRE(CHECK_COLUMN(result, 0, {1})); result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1", (int32_t)12); REQUIRE(CHECK_COLUMN(result, 0, {1})); result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1", (int64_t)12); REQUIRE(CHECK_COLUMN(result, 0, {1})); // create a prepared statement and use it to query auto prepare = con.Prepare("SELECT COUNT(*) FROM a WHERE i=$1"); result = prepare->Execute(12); REQUIRE(CHECK_COLUMN(result, 0, {1})); result = prepare->Execute(13); REQUIRE(CHECK_COLUMN(result, 0, {1})); REQUIRE(prepare->named_param_map.size() == 1); } TEST_CASE("Test type resolution of function with parameter expressions", "[api]") { DuckDB db(nullptr); Connection con(db); duckdb::unique_ptr result; con.EnableQueryVerification(); // can deduce type of prepared parameter here auto prepared = con.Prepare("select 1 + $1"); REQUIRE(!prepared->error.HasError()); result = prepared->Execute(1); REQUIRE(CHECK_COLUMN(result, 0, {2})); // no prepared statement REQUIRE_FAIL(con.SendQuery("SELECT ?")); } TEST_CASE("Test prepared statements and dependencies", "[api]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db), con2(db); REQUIRE_NO_FAIL(con.Query("CREATE TABLE a(i TINYINT)")); REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (11), (12), (13)")); // query using a prepared statement in con1 result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1", 12); REQUIRE(CHECK_COLUMN(result, 0, {1})); // now delete the table in con2 REQUIRE_NO_FAIL(con2.Query("DROP TABLE a")); REQUIRE_NO_FAIL(con.Query("CREATE TABLE a(i TINYINT)")); // keep a prepared statement around auto prepare = con.Prepare("SELECT COUNT(*) FROM a WHERE i=$1"); // we can drop the table REQUIRE_NO_FAIL(con2.Query("DROP TABLE a")); // now the prepared statement fails when executing REQUIRE_FAIL(prepare->Execute(11)); } TEST_CASE("Dropping connection with prepared statement resets dependencies", "[api]") { duckdb::unique_ptr result; DuckDB db(nullptr); auto con = make_uniq(db); Connection con2(db); REQUIRE_NO_FAIL(con->Query("CREATE TABLE a(i TINYINT)")); REQUIRE_NO_FAIL(con->Query("INSERT INTO a VALUES (11), (12), (13)")); auto prepared = con->Prepare("SELECT COUNT(*) FROM a WHERE i=$1"); result = prepared->Execute(12); REQUIRE(CHECK_COLUMN(result, 0, {1})); // we can drop the table REQUIRE_NO_FAIL(con2.Query("DROP TABLE a")); // after the table is dropped, the prepared statement no longer succeeds when run REQUIRE_FAIL(prepared->Execute(12)); REQUIRE_FAIL(prepared->Execute(12)); } TEST_CASE("Alter table and prepared statements", "[api]") { duckdb::unique_ptr result; DuckDB db(nullptr); auto con = make_uniq(db); Connection con2(db); REQUIRE_NO_FAIL(con->Query("CREATE TABLE a(i TINYINT)")); REQUIRE_NO_FAIL(con->Query("INSERT INTO a VALUES (11), (12), (13)")); auto prepared = con->Prepare("SELECT * FROM a WHERE i=$1"); result = prepared->Execute(12); REQUIRE(CHECK_COLUMN(result, 0, {12})); REQUIRE(prepared->ColumnCount() == 1); REQUIRE(prepared->GetStatementType() == StatementType::SELECT_STATEMENT); REQUIRE(prepared->GetTypes()[0].id() == LogicalTypeId::TINYINT); REQUIRE(prepared->GetNames()[0] == "i"); // we can alter the type of the column REQUIRE_NO_FAIL(con2.Query("ALTER TABLE a ALTER i TYPE BIGINT USING i")); // after the table is altered, the return types change, but the rebind is still successful result = prepared->Execute(12); REQUIRE(CHECK_COLUMN(result, 0, {12})); } TEST_CASE("Test destructors of prepared statements", "[api]") { duckdb::unique_ptr db; duckdb::unique_ptr con; duckdb::unique_ptr prepare; duckdb::unique_ptr result; // test destruction of connection db = make_uniq(nullptr); con = make_uniq(*db); // create a prepared statement prepare = con->Prepare("SELECT $1::INTEGER+$2::INTEGER"); // we can execute it result = prepare->Execute(3, 5); REQUIRE(CHECK_COLUMN(result, 0, {8})); // now destroy the connection con.reset(); // we can still use the prepared statement: the connection is alive until the prepared statement is dropped REQUIRE_NO_FAIL(prepare->Execute(3, 5)); // destroying the prepared statement is fine prepare.reset(); // test destruction of db // create a connection and prepared statement again con = make_uniq(*db); prepare = con->Prepare("SELECT $1::INTEGER+$2::INTEGER"); // we can execute it result = prepare->Execute(3, 5); REQUIRE(CHECK_COLUMN(result, 0, {8})); // destroy the db db.reset(); // we can still use the prepared statement REQUIRE_NO_FAIL(prepare->Execute(3, 5)); // and the connection REQUIRE_NO_FAIL(con->Query("SELECT 42")); // we can also prepare new statements prepare = con->Prepare("SELECT $1::INTEGER+$2::INTEGER"); REQUIRE(!prepare->HasError()); } TEST_CASE("Test incorrect usage of prepared statements API", "[api]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db); REQUIRE_NO_FAIL(con.Query("CREATE TABLE a (i TINYINT)")); REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (11), (12), (13)")); // this fails if there is a mismatch between number of arguments in prepare and in variadic // too few: REQUIRE_FAIL(con.Query("SELECT COUNT(*) FROM a WHERE i=$1 AND i>$2", 11)); // too many: REQUIRE_FAIL(con.Query("SELECT COUNT(*) FROM a WHERE i=$1 AND i>$2", 11, 13, 17)); // prepare an SQL string with a parse error auto prepare = con.Prepare("SELEC COUNT(*) FROM a WHERE i=$1"); // we cannot execute this prepared statement REQUIRE(prepare->HasError()); REQUIRE_FAIL(prepare->Execute(12)); // cannot prepare multiple statements at once prepare = con.Prepare("SELECT COUNT(*) FROM a WHERE i=$1; SELECT 42+$2;"); REQUIRE(prepare->HasError()); REQUIRE_FAIL(prepare->Execute(12)); // also not in the Query syntax REQUIRE_FAIL(con.Query("SELECT COUNT(*) FROM a WHERE i=$1; SELECT 42+$2", 11)); } TEST_CASE("Test multiple prepared statements", "[api]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db); REQUIRE_NO_FAIL(con.Query("CREATE TABLE a (i TINYINT)")); REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (11), (12), (13)")); // test that we can have multiple open prepared statements at a time auto prepare = con.Prepare("SELECT COUNT(*) FROM a WHERE i=$1"); auto prepare2 = con.Prepare("SELECT COUNT(*) FROM a WHERE i>$1"); result = prepare->Execute(12); REQUIRE(CHECK_COLUMN(result, 0, {1})); result = prepare2->Execute(11); REQUIRE(CHECK_COLUMN(result, 0, {2})); } TEST_CASE("Test prepared statements and transactions", "[api]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db); // create prepared statements in a transaction REQUIRE_NO_FAIL(con.Query("BEGIN TRANSACTION")); REQUIRE_NO_FAIL(con.Query("CREATE TABLE a (i TINYINT)")); REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (11), (12), (13)")); auto prepare = con.Prepare("SELECT COUNT(*) FROM a WHERE i=$1"); auto prepare2 = con.Prepare("SELECT COUNT(*) FROM a WHERE i>$1"); result = prepare->Execute(12); REQUIRE(CHECK_COLUMN(result, 0, {1})); result = prepare2->Execute(11); REQUIRE(CHECK_COLUMN(result, 0, {2})); // now if we rollback our prepared statements are invalidated REQUIRE_NO_FAIL(con.Query("ROLLBACK")); REQUIRE_FAIL(prepare->Execute(12)); REQUIRE_FAIL(prepare2->Execute(11)); } TEST_CASE("Test prepared statement parameter counting", "[api]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db); auto p0 = con.Prepare("SELECT 42"); REQUIRE(!p0->HasError()); REQUIRE(p0->named_param_map.empty()); auto p1 = con.Prepare("SELECT $1::int"); REQUIRE(!p1->HasError()); REQUIRE(p1->named_param_map.size() == 1); p1 = con.Prepare("SELECT ?::int"); REQUIRE(!p1->HasError()); REQUIRE(p1->named_param_map.size() == 1); auto p2 = con.Prepare("SELECT $1::int"); REQUIRE(!p2->HasError()); REQUIRE(p2->named_param_map.size() == 1); auto p3 = con.Prepare("SELECT ?::int, ?::string"); REQUIRE(!p3->HasError()); REQUIRE(p3->named_param_map.size() == 2); auto p4 = con.Prepare("SELECT $1::int, $2::string"); REQUIRE(!p4->HasError()); REQUIRE(p4->named_param_map.size() == 2); } TEST_CASE("Test ANALYZE", "[api]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db); con.EnableQueryVerification(); // ANALYZE runs without errors, note that ANALYZE is actually just ignored REQUIRE_NO_FAIL(con.Query("ANALYZE")); REQUIRE_NO_FAIL(con.Query("VACUUM")); auto prep = con.Prepare("ANALYZE"); REQUIRE(!prep->HasError()); auto res = prep->Execute(); REQUIRE(!res->HasError()); prep = con.Prepare("VACUUM"); REQUIRE(!prep->HasError()); res = prep->Execute(); REQUIRE(!res->HasError()); } TEST_CASE("Test DECIMAL with PreparedStatement", "[api]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db); auto ps = con.Prepare("SELECT $1::DECIMAL(4,1), $2::DECIMAL(9,1), $3::DECIMAL(18,3), $4::DECIMAL(38,8)"); result = ps->Execute(1.1, 100.1, 1401.123, "12481204981084098124.12398"); REQUIRE(CHECK_COLUMN(result, 0, {1.1})); REQUIRE(CHECK_COLUMN(result, 1, {100.1})); REQUIRE(CHECK_COLUMN(result, 2, {1401.123})); REQUIRE(CHECK_COLUMN(result, 3, {12481204981084098124.12398})); } TEST_CASE("Test BLOB with PreparedStatement", "[api]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db); // Creating a blob buffer with almost ALL ASCII chars uint8_t num_chars = 256 - 5; // skipping: '\0', '\n', '\15', ',', '\32' auto blob_chars = make_unsafe_uniq_array(num_chars); char ch = '\0'; idx_t buf_idx = 0; for (idx_t i = 0; i < 255; ++i, ++ch) { // skip chars: '\0', new line, shift in, comma, and crtl+Z if (ch == '\0' || ch == '\n' || ch == '\15' || ch == ',' || ch == '\32') { continue; } blob_chars[buf_idx] = ch; ++buf_idx; } REQUIRE_NO_FAIL(con.Query("CREATE TABLE blobs (b BYTEA);")); // Insert blob values through a PreparedStatement Value blob_val = Value::BLOB(const_data_ptr_cast(blob_chars.get()), num_chars); duckdb::unique_ptr ps = con.Prepare("INSERT INTO blobs VALUES (?::BYTEA)"); ps->Execute(blob_val); REQUIRE(!ps->HasError()); ps.reset(); // Testing if the bytes are stored correctly result = con.Query("SELECT OCTET_LENGTH(b) FROM blobs"); REQUIRE(CHECK_COLUMN(result, 0, {num_chars})); result = con.Query("SELECT count(b) FROM blobs"); REQUIRE(CHECK_COLUMN(result, 0, {1})); result = con.Query("SELECT b FROM blobs"); REQUIRE(CHECK_COLUMN(result, 0, {blob_val})); blob_chars.reset(); } TEST_CASE("PREPARE for INSERT with dates", "[prepared]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db); // prepared DATE insert REQUIRE_NO_FAIL(con.Query("CREATE TABLE dates(d DATE)")); REQUIRE_NO_FAIL(con.Query("PREPARE s1 AS INSERT INTO dates VALUES ($1)")); REQUIRE_NO_FAIL(con.Query("EXECUTE s1 (DATE '1992-01-01')")); result = con.Query("SELECT * FROM dates"); REQUIRE(CHECK_COLUMN(result, 0, {Value::DATE(1992, 1, 1)})); REQUIRE_NO_FAIL(con.Query("DELETE FROM dates")); auto prepared = con.Prepare("INSERT INTO dates VALUES ($1)"); REQUIRE_NO_FAIL(prepared->Execute(Value::DATE(1992, 1, 3))); result = con.Query("SELECT * FROM dates"); REQUIRE(CHECK_COLUMN(result, 0, {Value::DATE(1992, 1, 3)})); } TEST_CASE("PREPARE multiple statements", "[prepared]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db); string query = "SELECT $1::INTEGER; SELECT $1::INTEGER;"; // cannot prepare multiple statements like this auto prepared = con.Prepare(query); REQUIRE(prepared->HasError()); // we can use ExtractStatements to execute the individual statements though auto statements = con.ExtractStatements(query); for (auto &statement : statements) { string stmt = query.substr(statement->stmt_location, statement->stmt_length); prepared = con.Prepare(stmt); REQUIRE(!prepared->HasError()); result = prepared->Execute(1); REQUIRE(CHECK_COLUMN(result, 0, {1})); } } static duckdb::unique_ptr TestExecutePrepared(Connection &con, string query) { auto prepared = con.Prepare(query); REQUIRE(!prepared->HasError()); return prepared->Execute(); } TEST_CASE("Prepare all types of statements", "[prepared]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db); auto &fs = db.GetFileSystem(); string csv_path = TestCreatePath("prepared_files"); if (fs.DirectoryExists(csv_path)) { fs.RemoveDirectory(csv_path); } // TRANSACTION REQUIRE_NO_FAIL(TestExecutePrepared(con, "BEGIN TRANSACTION")); // SELECT result = TestExecutePrepared(con, "SELECT 42"); REQUIRE(CHECK_COLUMN(result, 0, {42})); // CREATE_SCHEMA REQUIRE_NO_FAIL(TestExecutePrepared(con, "CREATE SCHEMA test")); // CREATE_TABLE REQUIRE_NO_FAIL(TestExecutePrepared(con, "CREATE TABLE test.a(i INTEGER)")); // CREATE_TABLE REQUIRE_NO_FAIL(TestExecutePrepared(con, "CREATE TABLE b(i INTEGER)")); // CREATE_INDEX REQUIRE_NO_FAIL(TestExecutePrepared(con, "CREATE INDEX i_index ON test.a(i)")); // CREATE_VIEW REQUIRE_NO_FAIL(TestExecutePrepared(con, "CREATE VIEW v1 AS SELECT * FROM test.a WHERE i=2")); // CREATE_SEQUENCE REQUIRE_NO_FAIL(TestExecutePrepared(con, "CREATE SEQUENCE seq")); // PRAGMA REQUIRE_NO_FAIL(TestExecutePrepared(con, "PRAGMA table_info('b')")); // EXPLAIN REQUIRE_NO_FAIL(TestExecutePrepared(con, "EXPLAIN SELECT 42")); // COPY REQUIRE_NO_FAIL(TestExecutePrepared(con, "COPY test.a TO '" + csv_path + "'")); // INSERT REQUIRE_NO_FAIL(TestExecutePrepared(con, "INSERT INTO test.a VALUES (1), (2), (3)")); // UPDATE REQUIRE_NO_FAIL(TestExecutePrepared(con, "UPDATE test.a SET i=i+1")); // DELETE REQUIRE_NO_FAIL(TestExecutePrepared(con, "DELETE FROM test.a WHERE i<4")); // PREPARE REQUIRE_NO_FAIL(TestExecutePrepared(con, "PREPARE p1 AS SELECT * FROM test.a")); // EXECUTE result = TestExecutePrepared(con, "EXECUTE p1"); REQUIRE(CHECK_COLUMN(result, 0, {4})); // DROP REQUIRE_NO_FAIL(TestExecutePrepared(con, "DROP SEQUENCE seq")); REQUIRE_NO_FAIL(TestExecutePrepared(con, "DROP VIEW v1")); REQUIRE_NO_FAIL(TestExecutePrepared(con, "DROP TABLE test.a CASCADE")); REQUIRE_NO_FAIL(TestExecutePrepared(con, "DROP SCHEMA test CASCADE")); // TRANSACTION REQUIRE_NO_FAIL(TestExecutePrepared(con, "COMMIT")); } TEST_CASE("Test ambiguous prepared statement parameter types", "[api]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db); result = con.Query("SELECT ?", 42); REQUIRE(CHECK_COLUMN(result, 0, {42})); result = con.Query("SELECT ?", "hello"); REQUIRE(CHECK_COLUMN(result, 0, {"hello"})); auto prep = con.Prepare("SELECT ?"); result = prep->Execute(42); REQUIRE(CHECK_COLUMN(result, 0, {42})); result = prep->Execute("hello"); REQUIRE(CHECK_COLUMN(result, 0, {"hello"})); } TEST_CASE("Test prepared statements with SET", "[api]") { duckdb::unique_ptr result; DuckDB db(nullptr); Connection con(db); con.EnableQueryVerification(); // create a prepared statement and use it to query auto prepare = con.Prepare("SET default_null_order=$1"); REQUIRE(prepare->success); // too many parameters REQUIRE_FAIL(prepare->Execute("xxx", "yyy")); // too few parameters REQUIRE_FAIL(prepare->Execute()); // unsupported setting REQUIRE_FAIL(prepare->Execute("unsupported_mode")); // this works REQUIRE_NO_FAIL(prepare->Execute("NULLS FIRST")); } TEST_CASE("Test prepared statements that require rebind", "[api]") { DuckDB db(nullptr); Connection con1(db); con1.EnableQueryVerification(); auto prepared = con1.Prepare("DROP TABLE IF EXISTS t1"); Connection con2(db); REQUIRE_NO_FAIL(con2.Query("CREATE OR REPLACE TABLE t1 (c1 varchar)")); REQUIRE_NO_FAIL(prepared->Execute()); } class TestExtensionState : public ClientContextState { public: bool CanRequestRebind() override { return true; } }; TEST_CASE("Test prepared statements with extension that can request a rebind", "[api]") { DuckDB db(nullptr); Connection con(db); REQUIRE_NO_FAIL(con.Query("CREATE OR REPLACE TABLE t1 (c1 INTEGER)")); // https://github.com/duckdb/duckdb/pull/11096 con.context->registered_state->Insert("test_extension", make_shared_ptr()); // SelectStatement REQUIRE_NO_FAIL(con.Prepare("SELECT ?")->Execute(42)); // InsertStatement REQUIRE_NO_FAIL(con.Prepare("INSERT INTO t1 VALUES(?)")->Execute(42)); // UpdateStatement REQUIRE_NO_FAIL(con.Prepare("UPDATE t1 SET c1 = ?")->Execute(43)); // SetVariableStatement REQUIRE_NO_FAIL(con.Prepare("SET VARIABLE test_var = ?")->Execute(42)); }