1#include "duckdb/main/capi/capi_internal.hpp"
2#include "duckdb/common/types/timestamp.hpp"
3#include "duckdb/common/allocator.hpp"
4
5namespace duckdb {
6
7struct CBaseConverter {
8 template <class DST>
9 static void NullConvert(DST &target) {
10 }
11};
12struct CStandardConverter : public CBaseConverter {
13 template <class SRC, class DST>
14 static DST Convert(SRC input) {
15 return input;
16 }
17};
18
19struct CStringConverter {
20 template <class SRC, class DST>
21 static DST Convert(SRC input) {
22 auto result = char_ptr_cast(duckdb_malloc(input.GetSize() + 1));
23 assert(result);
24 memcpy((void *)result, input.GetData(), input.GetSize());
25 auto write_arr = char_ptr_cast(result);
26 write_arr[input.GetSize()] = '\0';
27 return result;
28 }
29
30 template <class DST>
31 static void NullConvert(DST &target) {
32 target = nullptr;
33 }
34};
35
36struct CBlobConverter {
37 template <class SRC, class DST>
38 static DST Convert(SRC input) {
39 duckdb_blob result;
40 result.data = char_ptr_cast(duckdb_malloc(input.GetSize()));
41 result.size = input.GetSize();
42 assert(result.data);
43 memcpy(result.data, input.GetData(), input.GetSize());
44 return result;
45 }
46
47 template <class DST>
48 static void NullConvert(DST &target) {
49 target.data = nullptr;
50 target.size = 0;
51 }
52};
53
54struct CTimestampMsConverter : public CBaseConverter {
55 template <class SRC, class DST>
56 static DST Convert(SRC input) {
57 return Timestamp::FromEpochMs(ms: input.value);
58 }
59};
60
61struct CTimestampNsConverter : public CBaseConverter {
62 template <class SRC, class DST>
63 static DST Convert(SRC input) {
64 return Timestamp::FromEpochNanoSeconds(micros: input.value);
65 }
66};
67
68struct CTimestampSecConverter : public CBaseConverter {
69 template <class SRC, class DST>
70 static DST Convert(SRC input) {
71 return Timestamp::FromEpochSeconds(ms: input.value);
72 }
73};
74
75struct CHugeintConverter : public CBaseConverter {
76 template <class SRC, class DST>
77 static DST Convert(SRC input) {
78 duckdb_hugeint result;
79 result.lower = input.lower;
80 result.upper = input.upper;
81 return result;
82 }
83};
84
85struct CIntervalConverter : public CBaseConverter {
86 template <class SRC, class DST>
87 static DST Convert(SRC input) {
88 duckdb_interval result;
89 result.days = input.days;
90 result.months = input.months;
91 result.micros = input.micros;
92 return result;
93 }
94};
95
96template <class T>
97struct CDecimalConverter : public CBaseConverter {
98 template <class SRC, class DST>
99 static DST Convert(SRC input) {
100 duckdb_hugeint result;
101 result.lower = input;
102 result.upper = 0;
103 return result;
104 }
105};
106
107template <class SRC, class DST = SRC, class OP = CStandardConverter>
108void WriteData(duckdb_column *column, ColumnDataCollection &source, const vector<column_t> &column_ids) {
109 idx_t row = 0;
110 auto target = (DST *)column->__deprecated_data;
111 for (auto &input : source.Chunks(column_ids)) {
112 auto source = FlatVector::GetData<SRC>(input.data[0]);
113 auto &mask = FlatVector::Validity(vector&: input.data[0]);
114
115 for (idx_t k = 0; k < input.size(); k++, row++) {
116 if (!mask.RowIsValid(row_idx: k)) {
117 OP::template NullConvert<DST>(target[row]);
118 } else {
119 target[row] = OP::template Convert<SRC, DST>(source[k]);
120 }
121 }
122 }
123}
124
125duckdb_state deprecated_duckdb_translate_column(MaterializedQueryResult &result, duckdb_column *column, idx_t col) {
126 D_ASSERT(!result.HasError());
127 auto &collection = result.Collection();
128 idx_t row_count = collection.Count();
129 column->__deprecated_nullmask = (bool *)duckdb_malloc(size: sizeof(bool) * collection.Count());
130 column->__deprecated_data = duckdb_malloc(size: GetCTypeSize(type: column->__deprecated_type) * row_count);
131 if (!column->__deprecated_nullmask || !column->__deprecated_data) { // LCOV_EXCL_START
132 // malloc failure
133 return DuckDBError;
134 } // LCOV_EXCL_STOP
135
136 vector<column_t> column_ids {col};
137 // first convert the nullmask
138 {
139 idx_t row = 0;
140 for (auto &input : collection.Chunks(column_ids)) {
141 for (idx_t k = 0; k < input.size(); k++) {
142 column->__deprecated_nullmask[row++] = FlatVector::IsNull(vector: input.data[0], idx: k);
143 }
144 }
145 }
146 // then write the data
147 switch (result.types[col].id()) {
148 case LogicalTypeId::BOOLEAN:
149 WriteData<bool>(column, source&: collection, column_ids);
150 break;
151 case LogicalTypeId::TINYINT:
152 WriteData<int8_t>(column, source&: collection, column_ids);
153 break;
154 case LogicalTypeId::SMALLINT:
155 WriteData<int16_t>(column, source&: collection, column_ids);
156 break;
157 case LogicalTypeId::INTEGER:
158 WriteData<int32_t>(column, source&: collection, column_ids);
159 break;
160 case LogicalTypeId::BIGINT:
161 WriteData<int64_t>(column, source&: collection, column_ids);
162 break;
163 case LogicalTypeId::UTINYINT:
164 WriteData<uint8_t>(column, source&: collection, column_ids);
165 break;
166 case LogicalTypeId::USMALLINT:
167 WriteData<uint16_t>(column, source&: collection, column_ids);
168 break;
169 case LogicalTypeId::UINTEGER:
170 WriteData<uint32_t>(column, source&: collection, column_ids);
171 break;
172 case LogicalTypeId::UBIGINT:
173 WriteData<uint64_t>(column, source&: collection, column_ids);
174 break;
175 case LogicalTypeId::FLOAT:
176 WriteData<float>(column, source&: collection, column_ids);
177 break;
178 case LogicalTypeId::DOUBLE:
179 WriteData<double>(column, source&: collection, column_ids);
180 break;
181 case LogicalTypeId::DATE:
182 WriteData<date_t>(column, source&: collection, column_ids);
183 break;
184 case LogicalTypeId::TIME:
185 case LogicalTypeId::TIME_TZ:
186 WriteData<dtime_t>(column, source&: collection, column_ids);
187 break;
188 case LogicalTypeId::TIMESTAMP:
189 case LogicalTypeId::TIMESTAMP_TZ:
190 WriteData<timestamp_t>(column, source&: collection, column_ids);
191 break;
192 case LogicalTypeId::VARCHAR: {
193 WriteData<string_t, const char *, CStringConverter>(column, source&: collection, column_ids);
194 break;
195 }
196 case LogicalTypeId::BLOB: {
197 WriteData<string_t, duckdb_blob, CBlobConverter>(column, source&: collection, column_ids);
198 break;
199 }
200 case LogicalTypeId::TIMESTAMP_NS: {
201 WriteData<timestamp_t, timestamp_t, CTimestampNsConverter>(column, source&: collection, column_ids);
202 break;
203 }
204 case LogicalTypeId::TIMESTAMP_MS: {
205 WriteData<timestamp_t, timestamp_t, CTimestampMsConverter>(column, source&: collection, column_ids);
206 break;
207 }
208 case LogicalTypeId::TIMESTAMP_SEC: {
209 WriteData<timestamp_t, timestamp_t, CTimestampSecConverter>(column, source&: collection, column_ids);
210 break;
211 }
212 case LogicalTypeId::HUGEINT: {
213 WriteData<hugeint_t, duckdb_hugeint, CHugeintConverter>(column, source&: collection, column_ids);
214 break;
215 }
216 case LogicalTypeId::INTERVAL: {
217 WriteData<interval_t, duckdb_interval, CIntervalConverter>(column, source&: collection, column_ids);
218 break;
219 }
220 case LogicalTypeId::DECIMAL: {
221 // get data
222 switch (result.types[col].InternalType()) {
223 case PhysicalType::INT16: {
224 WriteData<int16_t, duckdb_hugeint, CDecimalConverter<int16_t>>(column, source&: collection, column_ids);
225 break;
226 }
227 case PhysicalType::INT32: {
228 WriteData<int32_t, duckdb_hugeint, CDecimalConverter<int32_t>>(column, source&: collection, column_ids);
229 break;
230 }
231 case PhysicalType::INT64: {
232 WriteData<int64_t, duckdb_hugeint, CDecimalConverter<int64_t>>(column, source&: collection, column_ids);
233 break;
234 }
235 case PhysicalType::INT128: {
236 WriteData<hugeint_t, duckdb_hugeint, CHugeintConverter>(column, source&: collection, column_ids);
237 break;
238 }
239 default:
240 throw std::runtime_error("Unsupported physical type for Decimal" +
241 TypeIdToString(type: result.types[col].InternalType()));
242 }
243 break;
244 }
245 default: // LCOV_EXCL_START
246 return DuckDBError;
247 } // LCOV_EXCL_STOP
248 return DuckDBSuccess;
249}
250
251duckdb_state duckdb_translate_result(unique_ptr<QueryResult> result_p, duckdb_result *out) {
252 auto &result = *result_p;
253 D_ASSERT(result_p);
254 if (!out) {
255 // no result to write to, only return the status
256 return !result.HasError() ? DuckDBSuccess : DuckDBError;
257 }
258
259 memset(s: out, c: 0, n: sizeof(duckdb_result));
260
261 // initialize the result_data object
262 auto result_data = new DuckDBResultData();
263 result_data->result = std::move(result_p);
264 result_data->result_set_type = CAPIResultSetType::CAPI_RESULT_TYPE_NONE;
265 out->internal_data = result_data;
266
267 if (result.HasError()) {
268 // write the error message
269 out->__deprecated_error_message = (char *)result.GetError().c_str(); // NOLINT
270 return DuckDBError;
271 }
272 // copy the data
273 // first write the meta data
274 out->__deprecated_column_count = result.ColumnCount();
275 out->__deprecated_rows_changed = 0;
276 return DuckDBSuccess;
277}
278
279bool deprecated_materialize_result(duckdb_result *result) {
280 if (!result) {
281 return false;
282 }
283 auto result_data = reinterpret_cast<duckdb::DuckDBResultData *>(result->internal_data);
284 if (result_data->result->HasError()) {
285 return false;
286 }
287 if (result_data->result_set_type == CAPIResultSetType::CAPI_RESULT_TYPE_DEPRECATED) {
288 // already materialized into deprecated result format
289 return true;
290 }
291 if (result_data->result_set_type == CAPIResultSetType::CAPI_RESULT_TYPE_MATERIALIZED) {
292 // already used as a new result set
293 return false;
294 }
295 if (result_data->result_set_type == CAPIResultSetType::CAPI_RESULT_TYPE_STREAMING) {
296 // already used as a streaming result
297 return false;
298 }
299 // materialize as deprecated result set
300 result_data->result_set_type = CAPIResultSetType::CAPI_RESULT_TYPE_DEPRECATED;
301 auto column_count = result_data->result->ColumnCount();
302 result->__deprecated_columns = (duckdb_column *)duckdb_malloc(size: sizeof(duckdb_column) * column_count);
303 if (!result->__deprecated_columns) { // LCOV_EXCL_START
304 // malloc failure
305 return DuckDBError;
306 } // LCOV_EXCL_STOP
307 if (result_data->result->type == QueryResultType::STREAM_RESULT) {
308 // if we are dealing with a stream result, convert it to a materialized result first
309 auto &stream_result = (StreamQueryResult &)*result_data->result;
310 result_data->result = stream_result.Materialize();
311 }
312 D_ASSERT(result_data->result->type == QueryResultType::MATERIALIZED_RESULT);
313 auto &materialized = reinterpret_cast<MaterializedQueryResult &>(*result_data->result);
314
315 // convert the result to a materialized result
316 // zero initialize the columns (so we can cleanly delete it in case a malloc fails)
317 memset(s: result->__deprecated_columns, c: 0, n: sizeof(duckdb_column) * column_count);
318 for (idx_t i = 0; i < column_count; i++) {
319 result->__deprecated_columns[i].__deprecated_type = ConvertCPPTypeToC(sql_type: result_data->result->types[i]);
320 result->__deprecated_columns[i].__deprecated_name = (char *)result_data->result->names[i].c_str(); // NOLINT
321 }
322 result->__deprecated_row_count = materialized.RowCount();
323 if (result->__deprecated_row_count > 0 &&
324 materialized.properties.return_type == StatementReturnType::CHANGED_ROWS) {
325 // update total changes
326 auto row_changes = materialized.GetValue(column: 0, index: 0);
327 if (!row_changes.IsNull() && row_changes.DefaultTryCastAs(target_type: LogicalType::BIGINT)) {
328 result->__deprecated_rows_changed = row_changes.GetValue<int64_t>();
329 }
330 }
331 // now write the data
332 for (idx_t col = 0; col < column_count; col++) {
333 auto state = deprecated_duckdb_translate_column(result&: materialized, column: &result->__deprecated_columns[col], col);
334 if (state != DuckDBSuccess) {
335 return false;
336 }
337 }
338 return true;
339}
340
341} // namespace duckdb
342
343static void DuckdbDestroyColumn(duckdb_column column, idx_t count) {
344 if (column.__deprecated_data) {
345 if (column.__deprecated_type == DUCKDB_TYPE_VARCHAR) {
346 // varchar, delete individual strings
347 auto data = reinterpret_cast<char **>(column.__deprecated_data);
348 for (idx_t i = 0; i < count; i++) {
349 if (data[i]) {
350 duckdb_free(ptr: data[i]);
351 }
352 }
353 } else if (column.__deprecated_type == DUCKDB_TYPE_BLOB) {
354 // blob, delete individual blobs
355 auto data = reinterpret_cast<duckdb_blob *>(column.__deprecated_data);
356 for (idx_t i = 0; i < count; i++) {
357 if (data[i].data) {
358 duckdb_free(ptr: (void *)data[i].data);
359 }
360 }
361 }
362 duckdb_free(ptr: column.__deprecated_data);
363 }
364 if (column.__deprecated_nullmask) {
365 duckdb_free(ptr: column.__deprecated_nullmask);
366 }
367}
368
369void duckdb_destroy_result(duckdb_result *result) {
370 if (result->__deprecated_columns) {
371 for (idx_t i = 0; i < result->__deprecated_column_count; i++) {
372 DuckdbDestroyColumn(column: result->__deprecated_columns[i], count: result->__deprecated_row_count);
373 }
374 duckdb_free(ptr: result->__deprecated_columns);
375 }
376 if (result->internal_data) {
377 auto result_data = reinterpret_cast<duckdb::DuckDBResultData *>(result->internal_data);
378 delete result_data;
379 }
380 memset(s: result, c: 0, n: sizeof(duckdb_result));
381}
382
383const char *duckdb_column_name(duckdb_result *result, idx_t col) {
384 if (!result || col >= duckdb_column_count(result)) {
385 return nullptr;
386 }
387 auto &result_data = *(reinterpret_cast<duckdb::DuckDBResultData *>(result->internal_data));
388 return result_data.result->names[col].c_str();
389}
390
391duckdb_type duckdb_column_type(duckdb_result *result, idx_t col) {
392 if (!result || col >= duckdb_column_count(result)) {
393 return DUCKDB_TYPE_INVALID;
394 }
395 auto &result_data = *(reinterpret_cast<duckdb::DuckDBResultData *>(result->internal_data));
396 return duckdb::ConvertCPPTypeToC(sql_type: result_data.result->types[col]);
397}
398
399duckdb_logical_type duckdb_column_logical_type(duckdb_result *result, idx_t col) {
400 if (!result || col >= duckdb_column_count(result)) {
401 return nullptr;
402 }
403 auto &result_data = *(reinterpret_cast<duckdb::DuckDBResultData *>(result->internal_data));
404 return reinterpret_cast<duckdb_logical_type>(new duckdb::LogicalType(result_data.result->types[col]));
405}
406
407idx_t duckdb_column_count(duckdb_result *result) {
408 if (!result) {
409 return 0;
410 }
411 auto &result_data = *(reinterpret_cast<duckdb::DuckDBResultData *>(result->internal_data));
412 return result_data.result->ColumnCount();
413}
414
415idx_t duckdb_row_count(duckdb_result *result) {
416 if (!result) {
417 return 0;
418 }
419 auto &result_data = *(reinterpret_cast<duckdb::DuckDBResultData *>(result->internal_data));
420 if (result_data.result->type == duckdb::QueryResultType::STREAM_RESULT) {
421 // We can't know the row count beforehand
422 return 0;
423 }
424 auto &materialized = reinterpret_cast<duckdb::MaterializedQueryResult &>(*result_data.result);
425 return materialized.RowCount();
426}
427
428idx_t duckdb_rows_changed(duckdb_result *result) {
429 if (!result) {
430 return 0;
431 }
432 if (!duckdb::deprecated_materialize_result(result)) {
433 return 0;
434 }
435 return result->__deprecated_rows_changed;
436}
437
438void *duckdb_column_data(duckdb_result *result, idx_t col) {
439 if (!result || col >= result->__deprecated_column_count) {
440 return nullptr;
441 }
442 if (!duckdb::deprecated_materialize_result(result)) {
443 return nullptr;
444 }
445 return result->__deprecated_columns[col].__deprecated_data;
446}
447
448bool *duckdb_nullmask_data(duckdb_result *result, idx_t col) {
449 if (!result || col >= result->__deprecated_column_count) {
450 return nullptr;
451 }
452 if (!duckdb::deprecated_materialize_result(result)) {
453 return nullptr;
454 }
455 return result->__deprecated_columns[col].__deprecated_nullmask;
456}
457
458const char *duckdb_result_error(duckdb_result *result) {
459 if (!result) {
460 return nullptr;
461 }
462 auto &result_data = *(reinterpret_cast<duckdb::DuckDBResultData *>(result->internal_data));
463 return !result_data.result->HasError() ? nullptr : result_data.result->GetError().c_str();
464}
465
466idx_t duckdb_result_chunk_count(duckdb_result result) {
467 if (!result.internal_data) {
468 return 0;
469 }
470 auto &result_data = *(reinterpret_cast<duckdb::DuckDBResultData *>(result.internal_data));
471 if (result_data.result_set_type == duckdb::CAPIResultSetType::CAPI_RESULT_TYPE_DEPRECATED) {
472 return 0;
473 }
474 if (result_data.result->type != duckdb::QueryResultType::MATERIALIZED_RESULT) {
475 // Can't know beforehand how many chunks are returned.
476 return 0;
477 }
478 auto &materialized = reinterpret_cast<duckdb::MaterializedQueryResult &>(*result_data.result);
479 return materialized.Collection().ChunkCount();
480}
481
482duckdb_data_chunk duckdb_result_get_chunk(duckdb_result result, idx_t chunk_idx) {
483 if (!result.internal_data) {
484 return nullptr;
485 }
486 auto &result_data = *(reinterpret_cast<duckdb::DuckDBResultData *>(result.internal_data));
487 if (result_data.result_set_type == duckdb::CAPIResultSetType::CAPI_RESULT_TYPE_DEPRECATED) {
488 return nullptr;
489 }
490 if (result_data.result->type != duckdb::QueryResultType::MATERIALIZED_RESULT) {
491 // This API is only supported for materialized query results
492 return nullptr;
493 }
494 result_data.result_set_type = duckdb::CAPIResultSetType::CAPI_RESULT_TYPE_MATERIALIZED;
495 auto &materialized = reinterpret_cast<duckdb::MaterializedQueryResult &>(*result_data.result);
496 auto &collection = materialized.Collection();
497 if (chunk_idx >= collection.ChunkCount()) {
498 return nullptr;
499 }
500 auto chunk = duckdb::make_uniq<duckdb::DataChunk>();
501 chunk->Initialize(allocator&: duckdb::Allocator::DefaultAllocator(), types: collection.Types());
502 collection.FetchChunk(chunk_idx, result&: *chunk);
503 return reinterpret_cast<duckdb_data_chunk>(chunk.release());
504}
505
506bool duckdb_result_is_streaming(duckdb_result result) {
507 if (!result.internal_data) {
508 return false;
509 }
510 if (duckdb_result_error(result: &result) != nullptr) {
511 return false;
512 }
513 auto &result_data = *(reinterpret_cast<duckdb::DuckDBResultData *>(result.internal_data));
514 return result_data.result->type == duckdb::QueryResultType::STREAM_RESULT;
515}
516