1#include "catch.hpp"
2#include "duckdb.h"
3#include "test_helpers.hpp"
4#include "duckdb/common/exception.hpp"
5
6using namespace duckdb;
7using namespace std;
8
9class CAPIResult {
10public:
11 ~CAPIResult() {
12 duckdb_destroy_result(&result);
13 }
14 void Query(duckdb_connection connection, string query) {
15 success = (duckdb_query(connection, query.c_str(), &result) == DuckDBSuccess);
16 }
17
18 idx_t column_count() {
19 return result.column_count;
20 }
21
22 idx_t row_count() {
23 return result.row_count;
24 }
25
26 template <class T> T Fetch(idx_t col, idx_t row) {
27 throw NotImplementedException("Unimplemented type for fetch");
28 }
29
30 bool IsNull(idx_t col, idx_t row) {
31 return result.columns[col].nullmask[row];
32 }
33
34public:
35 bool success = false;
36
37private:
38 duckdb_result result;
39};
40
41static bool NO_FAIL(CAPIResult &result) {
42 return result.success;
43}
44
45static bool NO_FAIL(unique_ptr<CAPIResult> result) {
46 return NO_FAIL(*result);
47}
48
49template <> bool CAPIResult::Fetch(idx_t col, idx_t row) {
50 return duckdb_value_boolean(&result, col, row);
51}
52
53template <> int8_t CAPIResult::Fetch(idx_t col, idx_t row) {
54 return duckdb_value_int8(&result, col, row);
55}
56
57template <> int16_t CAPIResult::Fetch(idx_t col, idx_t row) {
58 return duckdb_value_int16(&result, col, row);
59}
60
61template <> int32_t CAPIResult::Fetch(idx_t col, idx_t row) {
62 return duckdb_value_int32(&result, col, row);
63}
64
65template <> int64_t CAPIResult::Fetch(idx_t col, idx_t row) {
66 return duckdb_value_int64(&result, col, row);
67}
68
69template <> float CAPIResult::Fetch(idx_t col, idx_t row) {
70 return duckdb_value_float(&result, col, row);
71}
72
73template <> double CAPIResult::Fetch(idx_t col, idx_t row) {
74 return duckdb_value_double(&result, col, row);
75}
76
77template <> duckdb_date CAPIResult::Fetch(idx_t col, idx_t row) {
78 auto data = (duckdb_date *)result.columns[col].data;
79 return data[row];
80}
81
82template <> duckdb_timestamp CAPIResult::Fetch(idx_t col, idx_t row) {
83 auto data = (duckdb_timestamp *)result.columns[col].data;
84 return data[row];
85}
86
87template <> string CAPIResult::Fetch(idx_t col, idx_t row) {
88 auto value = duckdb_value_varchar(&result, col, row);
89 string strval = string(value);
90 free((void *)value);
91 return strval;
92}
93
94class CAPITester {
95public:
96 CAPITester() : database(nullptr), connection(nullptr) {
97 }
98 ~CAPITester() {
99 Cleanup();
100 }
101
102 void Cleanup() {
103 if (connection) {
104 duckdb_disconnect(&connection);
105 connection = nullptr;
106 }
107 if (database) {
108 duckdb_close(&database);
109 database = nullptr;
110 }
111 }
112
113 bool OpenDatabase(const char *path) {
114 Cleanup();
115 if (duckdb_open(path, &database) != DuckDBSuccess) {
116 return false;
117 }
118 if (duckdb_connect(database, &connection) != DuckDBSuccess) {
119 return false;
120 }
121 return true;
122 }
123
124 unique_ptr<CAPIResult> Query(string query) {
125 auto result = make_unique<CAPIResult>();
126 result->Query(connection, query);
127 return result;
128 }
129
130 duckdb_database database = nullptr;
131 duckdb_connection connection = nullptr;
132};
133
134TEST_CASE("Basic test of C API", "[capi]") {
135 CAPITester tester;
136 unique_ptr<CAPIResult> result;
137
138 // open the database in in-memory mode
139 REQUIRE(tester.OpenDatabase(nullptr));
140
141 // select scalar value
142 result = tester.Query("SELECT CAST(42 AS BIGINT)");
143 REQUIRE_NO_FAIL(*result);
144 REQUIRE(result->column_count() == 1);
145 REQUIRE(result->row_count() == 1);
146 REQUIRE(result->Fetch<int64_t>(0, 0) == 42);
147 REQUIRE(!result->IsNull(0, 0));
148
149 // select scalar NULL
150 result = tester.Query("SELECT NULL");
151 REQUIRE_NO_FAIL(*result);
152 REQUIRE(result->column_count() == 1);
153 REQUIRE(result->row_count() == 1);
154 REQUIRE(result->Fetch<int64_t>(0, 0) == 0);
155 REQUIRE(result->IsNull(0, 0));
156
157 // select scalar string
158 result = tester.Query("SELECT 'hello'");
159 REQUIRE_NO_FAIL(*result);
160 REQUIRE(result->column_count() == 1);
161 REQUIRE(result->row_count() == 1);
162 REQUIRE(result->Fetch<string>(0, 0) == "hello");
163 REQUIRE(!result->IsNull(0, 0));
164
165 // multiple insertions
166 REQUIRE_NO_FAIL(tester.Query("CREATE TABLE test (a INTEGER, b INTEGER);"));
167 REQUIRE_NO_FAIL(tester.Query("INSERT INTO test VALUES (11, 22)"));
168 REQUIRE_NO_FAIL(tester.Query("INSERT INTO test VALUES (NULL, 21)"));
169 REQUIRE_NO_FAIL(tester.Query("INSERT INTO test VALUES (13, 22)"));
170
171 // NULL selection
172 result = tester.Query("SELECT a, b FROM test ORDER BY a");
173 REQUIRE_NO_FAIL(*result);
174 // NULL, 11, 13
175 REQUIRE(result->IsNull(0, 0));
176 REQUIRE(result->Fetch<int32_t>(0, 1) == 11);
177 REQUIRE(result->Fetch<int32_t>(0, 2) == 13);
178 // 21, 22, 22
179 REQUIRE(result->Fetch<int32_t>(1, 0) == 21);
180 REQUIRE(result->Fetch<int32_t>(1, 1) == 22);
181 REQUIRE(result->Fetch<int32_t>(1, 2) == 22);
182}
183
184TEST_CASE("Test different types of C API", "[capi]") {
185 CAPITester tester;
186 unique_ptr<CAPIResult> result;
187
188 // open the database in in-memory mode
189 REQUIRE(tester.OpenDatabase(nullptr));
190
191 // integer columns
192 vector<string> types = {"TINYINT", "SMALLINT", "INTEGER", "BIGINT"};
193 for (auto &type : types) {
194 // create the table and insert values
195 REQUIRE_NO_FAIL(tester.Query("BEGIN TRANSACTION"));
196 REQUIRE_NO_FAIL(tester.Query("CREATE TABLE integers(i " + type + ")"));
197 REQUIRE_NO_FAIL(tester.Query("INSERT INTO integers VALUES (1), (NULL)"));
198
199 result = tester.Query("SELECT * FROM integers ORDER BY i");
200 REQUIRE_NO_FAIL(*result);
201 REQUIRE(result->IsNull(0, 0));
202 REQUIRE(result->Fetch<int8_t>(0, 0) == 0);
203 REQUIRE(result->Fetch<int16_t>(0, 0) == 0);
204 REQUIRE(result->Fetch<int32_t>(0, 0) == 0);
205 REQUIRE(result->Fetch<int64_t>(0, 0) == 0);
206 REQUIRE(ApproxEqual(result->Fetch<float>(0, 0), 0.0f));
207 REQUIRE(ApproxEqual(result->Fetch<double>(0, 0), 0.0));
208
209 REQUIRE(!result->IsNull(0, 1));
210 REQUIRE(result->Fetch<int8_t>(0, 1) == 1);
211 REQUIRE(result->Fetch<int16_t>(0, 1) == 1);
212 REQUIRE(result->Fetch<int32_t>(0, 1) == 1);
213 REQUIRE(result->Fetch<int64_t>(0, 1) == 1);
214 REQUIRE(ApproxEqual(result->Fetch<float>(0, 1), 1.0f));
215 REQUIRE(ApproxEqual(result->Fetch<double>(0, 1), 1.0));
216 REQUIRE(result->Fetch<string>(0, 1) == "1");
217
218 REQUIRE_NO_FAIL(tester.Query("ROLLBACK"));
219 }
220 // real/double columns
221 types = {"REAL", "DOUBLE"};
222 for (auto &type : types) {
223 // create the table and insert values
224 REQUIRE_NO_FAIL(tester.Query("BEGIN TRANSACTION"));
225 REQUIRE_NO_FAIL(tester.Query("CREATE TABLE doubles(i " + type + ")"));
226 REQUIRE_NO_FAIL(tester.Query("INSERT INTO doubles VALUES (1), (NULL)"));
227
228 result = tester.Query("SELECT * FROM doubles ORDER BY i");
229 REQUIRE_NO_FAIL(*result);
230 REQUIRE(result->IsNull(0, 0));
231 REQUIRE(result->Fetch<int8_t>(0, 0) == 0);
232 REQUIRE(result->Fetch<int16_t>(0, 0) == 0);
233 REQUIRE(result->Fetch<int32_t>(0, 0) == 0);
234 REQUIRE(result->Fetch<int64_t>(0, 0) == 0);
235 REQUIRE(ApproxEqual(result->Fetch<float>(0, 0), 0.0f));
236 REQUIRE(ApproxEqual(result->Fetch<double>(0, 0), 0.0));
237
238 REQUIRE(!result->IsNull(0, 1));
239 REQUIRE(result->Fetch<int8_t>(0, 1) == 1);
240 REQUIRE(result->Fetch<int16_t>(0, 1) == 1);
241 REQUIRE(result->Fetch<int32_t>(0, 1) == 1);
242 REQUIRE(result->Fetch<int64_t>(0, 1) == 1);
243 REQUIRE(ApproxEqual(result->Fetch<float>(0, 1), 1.0f));
244 REQUIRE(ApproxEqual(result->Fetch<double>(0, 1), 1.0));
245
246 REQUIRE_NO_FAIL(tester.Query("ROLLBACK"));
247 }
248 // date columns
249 REQUIRE_NO_FAIL(tester.Query("CREATE TABLE dates(d DATE)"));
250 REQUIRE_NO_FAIL(tester.Query("INSERT INTO dates VALUES ('1992-09-20'), (NULL), ('1000000-09-20')"));
251
252 result = tester.Query("SELECT * FROM dates ORDER BY d");
253 REQUIRE_NO_FAIL(*result);
254 REQUIRE(result->IsNull(0, 0));
255 duckdb_date date = result->Fetch<duckdb_date>(0, 1);
256 REQUIRE(date.year == 1992);
257 REQUIRE(date.month == 9);
258 REQUIRE(date.day == 20);
259 REQUIRE(result->Fetch<string>(0, 1) == Value::DATE(1992, 9, 20).ToString(SQLType::DATE));
260 date = result->Fetch<duckdb_date>(0, 2);
261 REQUIRE(date.year == 1000000);
262 REQUIRE(date.month == 9);
263 REQUIRE(date.day == 20);
264 REQUIRE(result->Fetch<string>(0, 2) == Value::DATE(1000000, 9, 20).ToString(SQLType::DATE));
265
266 // timestamp columns
267 REQUIRE_NO_FAIL(tester.Query("CREATE TABLE timestamps(t TIMESTAMP)"));
268 REQUIRE_NO_FAIL(tester.Query("INSERT INTO timestamps VALUES ('1992-09-20 12:01:30'), (NULL)"));
269
270 result = tester.Query("SELECT * FROM timestamps ORDER BY t");
271 REQUIRE_NO_FAIL(*result);
272 REQUIRE(result->IsNull(0, 0));
273 duckdb_timestamp stamp = result->Fetch<duckdb_timestamp>(0, 1);
274 REQUIRE(stamp.date.year == 1992);
275 REQUIRE(stamp.date.month == 9);
276 REQUIRE(stamp.date.day == 20);
277 REQUIRE(stamp.time.hour == 12);
278 REQUIRE(stamp.time.min == 1);
279 REQUIRE(stamp.time.sec == 30);
280 REQUIRE(stamp.time.msec == 0);
281 REQUIRE(result->Fetch<string>(0, 1) == Value::TIMESTAMP(1992, 9, 20, 12, 1, 30, 0).ToString(SQLType::TIMESTAMP));
282
283 // boolean columns
284 REQUIRE_NO_FAIL(tester.Query("CREATE TABLE booleans(b BOOLEAN)"));
285 REQUIRE_NO_FAIL(tester.Query("INSERT INTO booleans VALUES (42 > 60), (42 > 20), (42 > NULL)"));
286
287 result = tester.Query("SELECT * FROM booleans ORDER BY b");
288 REQUIRE_NO_FAIL(*result);
289 REQUIRE(result->IsNull(0, 0));
290 REQUIRE(!result->Fetch<bool>(0, 0));
291 REQUIRE(!result->Fetch<bool>(0, 1));
292 REQUIRE(result->Fetch<bool>(0, 2));
293 REQUIRE(result->Fetch<string>(0, 2) == Value::BOOLEAN(true).ToString());
294}
295
296TEST_CASE("Test errors in C API", "[capi]") {
297 CAPITester tester;
298 unique_ptr<CAPIResult> result;
299
300 // cannot open database in random directory
301 REQUIRE(!tester.OpenDatabase("/bla/this/directory/should/not/exist/hopefully/awerar333"));
302 REQUIRE(tester.OpenDatabase(nullptr));
303
304 // syntax error in query
305 REQUIRE_FAIL(tester.Query("SELEC * FROM TABLE"));
306 // bind error
307 REQUIRE_FAIL(tester.Query("SELECT * FROM TABLE"));
308
309 duckdb_result res;
310 duckdb_prepared_statement stmt = nullptr;
311 // fail prepare API calls
312 REQUIRE(duckdb_prepare(NULL, "SELECT 42", &stmt) == DuckDBError);
313 REQUIRE(duckdb_prepare(tester.connection, NULL, &stmt) == DuckDBError);
314 REQUIRE(duckdb_bind_boolean(NULL, 0, true) == DuckDBError);
315 REQUIRE(duckdb_execute_prepared(NULL, &res) == DuckDBError);
316 duckdb_destroy_prepare(NULL);
317}
318
319TEST_CASE("Test prepared statements in C API", "[capi]") {
320 CAPITester tester;
321 unique_ptr<CAPIResult> result;
322 duckdb_result res;
323 duckdb_prepared_statement stmt = nullptr;
324 duckdb_state status;
325
326 // open the database in in-memory mode
327 REQUIRE(tester.OpenDatabase(nullptr));
328
329 status = duckdb_prepare(tester.connection, "SELECT CAST($1 AS BIGINT)", &stmt);
330 REQUIRE(status == DuckDBSuccess);
331 REQUIRE(stmt != nullptr);
332
333 status = duckdb_bind_boolean(stmt, 1, 1);
334 REQUIRE(status == DuckDBSuccess);
335 status = duckdb_bind_boolean(stmt, 2, 1);
336 REQUIRE(status == DuckDBError);
337
338 status = duckdb_execute_prepared(stmt, &res);
339 REQUIRE(status == DuckDBSuccess);
340 REQUIRE(duckdb_value_int64(&res, 0, 0) == 1);
341 duckdb_destroy_result(&res);
342
343 duckdb_bind_int8(stmt, 1, 8);
344 status = duckdb_execute_prepared(stmt, &res);
345 REQUIRE(status == DuckDBSuccess);
346 REQUIRE(duckdb_value_int64(&res, 0, 0) == 8);
347 duckdb_destroy_result(&res);
348
349 duckdb_bind_int16(stmt, 1, 16);
350 status = duckdb_execute_prepared(stmt, &res);
351 REQUIRE(status == DuckDBSuccess);
352 REQUIRE(duckdb_value_int64(&res, 0, 0) == 16);
353 duckdb_destroy_result(&res);
354
355 duckdb_bind_int32(stmt, 1, 32);
356 status = duckdb_execute_prepared(stmt, &res);
357 REQUIRE(status == DuckDBSuccess);
358 REQUIRE(duckdb_value_int64(&res, 0, 0) == 32);
359 duckdb_destroy_result(&res);
360
361 duckdb_bind_int64(stmt, 1, 64);
362 status = duckdb_execute_prepared(stmt, &res);
363 REQUIRE(status == DuckDBSuccess);
364 REQUIRE(duckdb_value_int64(&res, 0, 0) == 64);
365 duckdb_destroy_result(&res);
366
367 duckdb_bind_float(stmt, 1, 42.0);
368 status = duckdb_execute_prepared(stmt, &res);
369 REQUIRE(status == DuckDBSuccess);
370 REQUIRE(duckdb_value_int64(&res, 0, 0) == 42);
371 duckdb_destroy_result(&res);
372
373 duckdb_bind_double(stmt, 1, 43.0);
374 status = duckdb_execute_prepared(stmt, &res);
375 REQUIRE(status == DuckDBSuccess);
376 REQUIRE(duckdb_value_int64(&res, 0, 0) == 43);
377 duckdb_destroy_result(&res);
378
379 duckdb_bind_varchar(stmt, 1, "44");
380 status = duckdb_execute_prepared(stmt, &res);
381 REQUIRE(status == DuckDBSuccess);
382 REQUIRE(duckdb_value_int64(&res, 0, 0) == 44);
383 duckdb_destroy_result(&res);
384
385 duckdb_bind_null(stmt, 1);
386 status = duckdb_execute_prepared(stmt, &res);
387 REQUIRE(status == DuckDBSuccess);
388 REQUIRE(res.columns[0].nullmask[0] == true);
389 duckdb_destroy_result(&res);
390
391 duckdb_destroy_prepare(&stmt);
392 // again to make sure it does not crash
393 duckdb_destroy_result(&res);
394 duckdb_destroy_prepare(&stmt);
395
396 status = duckdb_query(tester.connection, "CREATE TABLE a (i INTEGER)", NULL);
397 REQUIRE(status == DuckDBSuccess);
398
399 status = duckdb_prepare(tester.connection, "INSERT INTO a VALUES (?)", &stmt);
400 REQUIRE(status == DuckDBSuccess);
401 REQUIRE(stmt != nullptr);
402 idx_t nparams;
403 REQUIRE(duckdb_nparams(stmt, &nparams) == DuckDBSuccess);
404 REQUIRE(nparams == 1);
405
406 for (int32_t i = 1; i <= 1000; i++) {
407 duckdb_bind_int32(stmt, 1, i);
408 status = duckdb_execute_prepared(stmt, nullptr);
409 REQUIRE(status == DuckDBSuccess);
410 }
411 duckdb_destroy_prepare(&stmt);
412
413 status = duckdb_prepare(tester.connection, "SELECT SUM(i)*$1-$2 FROM a", &stmt);
414 REQUIRE(status == DuckDBSuccess);
415 REQUIRE(stmt != nullptr);
416 duckdb_bind_int32(stmt, 1, 2);
417 duckdb_bind_int32(stmt, 2, 1000);
418
419 status = duckdb_execute_prepared(stmt, &res);
420 REQUIRE(status == DuckDBSuccess);
421 REQUIRE(duckdb_value_int32(&res, 0, 0) == 1000000);
422 duckdb_destroy_result(&res);
423 duckdb_destroy_prepare(&stmt);
424
425 // not-so-happy path
426 status = duckdb_prepare(tester.connection, "SELECT XXXXX", &stmt);
427 REQUIRE(status == DuckDBError);
428 duckdb_destroy_prepare(&stmt);
429
430 status = duckdb_prepare(tester.connection, "SELECT CAST($1 AS INTEGER)", &stmt);
431 REQUIRE(status == DuckDBSuccess);
432 REQUIRE(stmt != nullptr);
433
434 status = duckdb_execute_prepared(stmt, &res);
435 REQUIRE(status == DuckDBError);
436 duckdb_destroy_result(&res);
437 duckdb_destroy_prepare(&stmt);
438}
439