| 1 | #include "catch.hpp" |
| 2 | #include "test_helpers.hpp" |
| 3 | |
| 4 | using namespace duckdb; |
| 5 | using namespace std; |
| 6 | |
| 7 | TEST_CASE("Test prepared statements API" , "[api]" ) { |
| 8 | unique_ptr<QueryResult> result; |
| 9 | DuckDB db(nullptr); |
| 10 | Connection con(db); |
| 11 | |
| 12 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE a (i TINYINT)" )); |
| 13 | REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (11), (12), (13)" )); |
| 14 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE strings(s VARCHAR)" )); |
| 15 | REQUIRE_NO_FAIL(con.Query("INSERT INTO strings VALUES (NULL), ('test')" )); |
| 16 | |
| 17 | // query using a prepared statement |
| 18 | // integer: |
| 19 | result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1" , 12); |
| 20 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
| 21 | // strings: |
| 22 | result = con.Query("SELECT COUNT(*) FROM strings WHERE s=$1" , "test" ); |
| 23 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
| 24 | // multiple parameters |
| 25 | result = con.Query("SELECT COUNT(*) FROM a WHERE i>$1 AND i<$2" , 10, 13); |
| 26 | REQUIRE(CHECK_COLUMN(result, 0, {2})); |
| 27 | |
| 28 | // test various integer types |
| 29 | result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1" , (int8_t)12); |
| 30 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
| 31 | result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1" , (int16_t)12); |
| 32 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
| 33 | result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1" , (int32_t)12); |
| 34 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
| 35 | result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1" , (int64_t)12); |
| 36 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
| 37 | |
| 38 | // create a prepared statement and use it to query |
| 39 | auto prepare = con.Prepare("SELECT COUNT(*) FROM a WHERE i=$1" ); |
| 40 | |
| 41 | result = prepare->Execute(12); |
| 42 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
| 43 | result = prepare->Execute(13); |
| 44 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
| 45 | REQUIRE(prepare->n_param == 1); |
| 46 | |
| 47 | string prepare_name = prepare->name; |
| 48 | // we can execute the prepared statement ourselves as well using the name |
| 49 | result = con.Query("EXECUTE " + prepare_name + "(12)" ); |
| 50 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
| 51 | // if we destroy the prepared statement it goes away |
| 52 | prepare.reset(); |
| 53 | REQUIRE_FAIL(con.Query("EXECUTE " + prepare_name + "(12)" )); |
| 54 | } |
| 55 | |
| 56 | TEST_CASE("Test prepared statements and dependencies" , "[api]" ) { |
| 57 | unique_ptr<QueryResult> result; |
| 58 | DuckDB db(nullptr); |
| 59 | Connection con(db), con2(db); |
| 60 | |
| 61 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE a(i TINYINT)" )); |
| 62 | REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (11), (12), (13)" )); |
| 63 | |
| 64 | // query using a prepared statement in con1 |
| 65 | result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1" , 12); |
| 66 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
| 67 | // now delete the table in con2 |
| 68 | REQUIRE_NO_FAIL(con2.Query("DROP TABLE a" )); |
| 69 | |
| 70 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE a(i TINYINT)" )); |
| 71 | |
| 72 | // keep a prepared statement around |
| 73 | auto prepare = con.Prepare("SELECT COUNT(*) FROM a WHERE i=$1" ); |
| 74 | |
| 75 | // now we can't drop the table |
| 76 | REQUIRE_FAIL(con2.Query("DROP TABLE a" )); |
| 77 | |
| 78 | // until we delete the prepared statement |
| 79 | prepare.reset(); |
| 80 | |
| 81 | REQUIRE_NO_FAIL(con2.Query("DROP TABLE a" )); |
| 82 | } |
| 83 | |
| 84 | TEST_CASE("Dropping connection with prepared statement resets dependencies" , "[api]" ) { |
| 85 | unique_ptr<QueryResult> result; |
| 86 | DuckDB db(nullptr); |
| 87 | auto con = make_unique<Connection>(db); |
| 88 | Connection con2(db); |
| 89 | |
| 90 | REQUIRE_NO_FAIL(con->Query("CREATE TABLE a(i TINYINT)" )); |
| 91 | REQUIRE_NO_FAIL(con->Query("INSERT INTO a VALUES (11), (12), (13)" )); |
| 92 | |
| 93 | auto prepared = con->Prepare("SELECT COUNT(*) FROM a WHERE i=$1" ); |
| 94 | result = prepared->Execute(12); |
| 95 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
| 96 | |
| 97 | // now we can't drop the table |
| 98 | REQUIRE_FAIL(con2.Query("DROP TABLE a" )); |
| 99 | |
| 100 | // now drop con |
| 101 | con.reset(); |
| 102 | |
| 103 | // now we can |
| 104 | REQUIRE_NO_FAIL(con2.Query("DROP TABLE a" )); |
| 105 | } |
| 106 | |
| 107 | TEST_CASE("Test destructors of prepared statements" , "[api]" ) { |
| 108 | unique_ptr<DuckDB> db; |
| 109 | unique_ptr<Connection> con; |
| 110 | unique_ptr<PreparedStatement> prepare; |
| 111 | unique_ptr<QueryResult> result; |
| 112 | |
| 113 | // test destruction of connection |
| 114 | db = make_unique<DuckDB>(nullptr); |
| 115 | con = make_unique<Connection>(*db); |
| 116 | // create a prepared statement |
| 117 | prepare = con->Prepare("SELECT $1::INTEGER+$2::INTEGER" ); |
| 118 | // we can execute it |
| 119 | result = prepare->Execute(3, 5); |
| 120 | REQUIRE(CHECK_COLUMN(result, 0, {8})); |
| 121 | // now destroy the connection |
| 122 | con.reset(); |
| 123 | // now we can't use the prepared statement anymore |
| 124 | REQUIRE_FAIL(prepare->Execute(3, 5)); |
| 125 | // destroying the prepared statement is fine |
| 126 | prepare.reset(); |
| 127 | |
| 128 | // test destruction of db |
| 129 | // create a connection and prepared statement again |
| 130 | con = make_unique<Connection>(*db); |
| 131 | prepare = con->Prepare("SELECT $1::INTEGER+$2::INTEGER" ); |
| 132 | // we can execute it |
| 133 | result = prepare->Execute(3, 5); |
| 134 | REQUIRE(CHECK_COLUMN(result, 0, {8})); |
| 135 | // destroy the db |
| 136 | db.reset(); |
| 137 | // now we can't use the prepared statement anymore |
| 138 | REQUIRE_FAIL(prepare->Execute(3, 5)); |
| 139 | // neither can we use the connection |
| 140 | REQUIRE_FAIL(con->Query("SELECT 42" )); |
| 141 | // or prepare new statements |
| 142 | prepare = con->Prepare("SELECT $1::INTEGER+$2::INTEGER" ); |
| 143 | REQUIRE(!prepare->success); |
| 144 | } |
| 145 | |
| 146 | TEST_CASE("Test incorrect usage of prepared statements API" , "[api]" ) { |
| 147 | unique_ptr<QueryResult> result; |
| 148 | DuckDB db(nullptr); |
| 149 | Connection con(db); |
| 150 | |
| 151 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE a (i TINYINT)" )); |
| 152 | REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (11), (12), (13)" )); |
| 153 | |
| 154 | // this fails if there is a mismatch between number of arguments in prepare and in variadic |
| 155 | // too few: |
| 156 | REQUIRE_FAIL(con.Query("SELECT COUNT(*) FROM a WHERE i=$1 AND i>$2" , 11)); |
| 157 | // too many: |
| 158 | REQUIRE_FAIL(con.Query("SELECT COUNT(*) FROM a WHERE i=$1 AND i>$2" , 11, 13, 17)); |
| 159 | |
| 160 | // prepare an SQL string with a parse error |
| 161 | auto prepare = con.Prepare("SELEC COUNT(*) FROM a WHERE i=$1" ); |
| 162 | // we cannot execute this prepared statement |
| 163 | REQUIRE_FAIL(prepare->Execute(12)); |
| 164 | |
| 165 | // cannot prepare multiple statements at once |
| 166 | prepare = con.Prepare("SELECT COUNT(*) FROM a WHERE i=$1; SELECT 42+$2;" ); |
| 167 | REQUIRE_FAIL(prepare->Execute(12)); |
| 168 | |
| 169 | // also not in the Query syntax |
| 170 | REQUIRE_FAIL(con.Query("SELECT COUNT(*) FROM a WHERE i=$1; SELECT 42+$2" , 11)); |
| 171 | } |
| 172 | |
| 173 | TEST_CASE("Test multiple prepared statements" , "[api]" ) { |
| 174 | unique_ptr<QueryResult> result; |
| 175 | DuckDB db(nullptr); |
| 176 | Connection con(db); |
| 177 | |
| 178 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE a (i TINYINT)" )); |
| 179 | REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (11), (12), (13)" )); |
| 180 | |
| 181 | // test that we can have multiple open prepared statements at a time |
| 182 | auto prepare = con.Prepare("SELECT COUNT(*) FROM a WHERE i=$1" ); |
| 183 | auto prepare2 = con.Prepare("SELECT COUNT(*) FROM a WHERE i>$1" ); |
| 184 | |
| 185 | result = prepare->Execute(12); |
| 186 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
| 187 | result = prepare2->Execute(11); |
| 188 | REQUIRE(CHECK_COLUMN(result, 0, {2})); |
| 189 | } |
| 190 | |
| 191 | TEST_CASE("Test prepared statements and transactions" , "[api]" ) { |
| 192 | unique_ptr<QueryResult> result; |
| 193 | DuckDB db(nullptr); |
| 194 | Connection con(db); |
| 195 | |
| 196 | // create prepared statements in a transaction |
| 197 | REQUIRE_NO_FAIL(con.Query("BEGIN TRANSACTION" )); |
| 198 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE a (i TINYINT)" )); |
| 199 | REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (11), (12), (13)" )); |
| 200 | |
| 201 | auto prepare = con.Prepare("SELECT COUNT(*) FROM a WHERE i=$1" ); |
| 202 | auto prepare2 = con.Prepare("SELECT COUNT(*) FROM a WHERE i>$1" ); |
| 203 | |
| 204 | result = prepare->Execute(12); |
| 205 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
| 206 | result = prepare2->Execute(11); |
| 207 | REQUIRE(CHECK_COLUMN(result, 0, {2})); |
| 208 | // now if we rollback our prepared statements are invalidated |
| 209 | REQUIRE_NO_FAIL(con.Query("ROLLBACK" )); |
| 210 | |
| 211 | REQUIRE_FAIL(prepare->Execute(12)); |
| 212 | REQUIRE_FAIL(prepare2->Execute(11)); |
| 213 | } |
| 214 | |
| 215 | TEST_CASE("Test prepared statement parameter counting" , "[api]" ) { |
| 216 | unique_ptr<QueryResult> result; |
| 217 | DuckDB db(nullptr); |
| 218 | Connection con(db); |
| 219 | |
| 220 | auto p0 = con.Prepare("SELECT 42" ); |
| 221 | REQUIRE(p0->success); |
| 222 | REQUIRE(p0->n_param == 0); |
| 223 | |
| 224 | auto p1 = con.Prepare("SELECT $1::int" ); |
| 225 | REQUIRE(p1->success); |
| 226 | REQUIRE(p1->n_param == 1); |
| 227 | |
| 228 | p1 = con.Prepare("SELECT ?::int" ); |
| 229 | REQUIRE(p1->success); |
| 230 | REQUIRE(p1->n_param == 1); |
| 231 | |
| 232 | auto p2 = con.Prepare("SELECT $1::int" ); |
| 233 | REQUIRE(p2->success); |
| 234 | REQUIRE(p2->n_param == 1); |
| 235 | |
| 236 | auto p3 = con.Prepare("SELECT ?::int, ?::string" ); |
| 237 | REQUIRE(p3->success); |
| 238 | REQUIRE(p3->n_param == 2); |
| 239 | |
| 240 | auto p4 = con.Prepare("SELECT $1::int, $2::string" ); |
| 241 | REQUIRE(p4->success); |
| 242 | REQUIRE(p4->n_param == 2); |
| 243 | |
| 244 | auto p5 = con.Prepare("SELECT $2::int, $2::string" ); |
| 245 | REQUIRE(!p5->success); |
| 246 | } |
| 247 | |