1 | #include "duckdb/main/capi/capi_internal.hpp" |
2 | #include "duckdb/common/types/timestamp.hpp" |
3 | #include "duckdb/common/allocator.hpp" |
4 | |
5 | namespace duckdb { |
6 | |
7 | struct CBaseConverter { |
8 | template <class DST> |
9 | static void NullConvert(DST &target) { |
10 | } |
11 | }; |
12 | struct CStandardConverter : public CBaseConverter { |
13 | template <class SRC, class DST> |
14 | static DST Convert(SRC input) { |
15 | return input; |
16 | } |
17 | }; |
18 | |
19 | struct 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 | |
36 | struct 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 | |
54 | struct 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 | |
61 | struct 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 | |
68 | struct 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 | |
75 | struct 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 | |
85 | struct 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 | |
96 | template <class T> |
97 | struct 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 | |
107 | template <class SRC, class DST = SRC, class OP = CStandardConverter> |
108 | void 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 | |
125 | duckdb_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 | |
251 | duckdb_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 | |
279 | bool 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 | |
343 | static 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 | |
369 | void 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 | |
383 | const 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 | |
391 | duckdb_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 | |
399 | duckdb_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 | |
407 | idx_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 | |
415 | idx_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 | |
428 | idx_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 | |
438 | void *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 | |
448 | bool *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 | |
458 | const 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 | |
466 | idx_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 | |
482 | duckdb_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 | |
506 | bool 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 | |