1#include "duckdb/common/adbc/adbc.hpp"
2#include "duckdb/common/adbc/adbc-init.hpp"
3
4#include "duckdb/common/string.hpp"
5#include "duckdb/common/string_util.hpp"
6
7#include "duckdb.h"
8#include "duckdb/common/arrow/arrow_wrapper.hpp"
9#include "duckdb/common/arrow/arrow.hpp"
10
11#ifndef DUCKDB_AMALGAMATION
12#include "duckdb/main/connection.hpp"
13#endif
14
15#include <string.h>
16#include <stdlib.h>
17
18// We must leak the symbols of the init function
19duckdb_adbc::AdbcStatusCode duckdb_adbc_init(size_t count, struct duckdb_adbc::AdbcDriver *driver,
20 struct duckdb_adbc::AdbcError *error) {
21 if (!driver) {
22 return ADBC_STATUS_INVALID_ARGUMENT;
23 }
24
25 driver->DatabaseNew = duckdb_adbc::DatabaseNew;
26 driver->DatabaseSetOption = duckdb_adbc::DatabaseSetOption;
27 driver->DatabaseInit = duckdb_adbc::DatabaseInit;
28 driver->DatabaseRelease = duckdb_adbc::DatabaseRelease;
29 driver->ConnectionNew = duckdb_adbc::ConnectionNew;
30 driver->ConnectionSetOption = duckdb_adbc::ConnectionSetOption;
31 driver->ConnectionInit = duckdb_adbc::ConnectionInit;
32 driver->ConnectionRelease = duckdb_adbc::ConnectionRelease;
33 driver->ConnectionGetTableTypes = duckdb_adbc::ConnectionGetTableTypes;
34 driver->StatementNew = duckdb_adbc::StatementNew;
35 driver->StatementRelease = duckdb_adbc::StatementRelease;
36 // driver->StatementBind = duckdb::adbc::StatementBind;
37 driver->StatementBindStream = duckdb_adbc::StatementBindStream;
38 driver->StatementExecuteQuery = duckdb_adbc::StatementExecuteQuery;
39 driver->StatementPrepare = duckdb_adbc::StatementPrepare;
40 driver->StatementSetOption = duckdb_adbc::StatementSetOption;
41 driver->StatementSetSqlQuery = duckdb_adbc::StatementSetSqlQuery;
42 driver->ConnectionGetObjects = duckdb_adbc::ConnectionGetObjects;
43 driver->ConnectionCommit = duckdb_adbc::ConnectionCommit;
44 driver->ConnectionRollback = duckdb_adbc::ConnectionRollback;
45 driver->ConnectionReadPartition = duckdb_adbc::ConnectionReadPartition;
46 driver->StatementExecutePartitions = duckdb_adbc::StatementExecutePartitions;
47 return ADBC_STATUS_OK;
48}
49
50namespace duckdb_adbc {
51AdbcStatusCode SetErrorMaybe(const void *result, AdbcError *error, const std::string &error_message) {
52 if (!error) {
53 return ADBC_STATUS_INVALID_ARGUMENT;
54 }
55 if (!result) {
56 SetError(error, message: error_message);
57 return ADBC_STATUS_INVALID_ARGUMENT;
58 }
59 return ADBC_STATUS_OK;
60}
61
62struct DuckDBAdbcDatabaseWrapper {
63 //! The DuckDB Database Configuration
64 ::duckdb_config config;
65 //! The DuckDB Database
66 ::duckdb_database database;
67 //! Path of Disk-Based Database or :memory: database
68 std::string path;
69};
70
71void InitiliazeADBCError(AdbcError *error) {
72 if (!error) {
73 return;
74 }
75 error->message = nullptr;
76 error->release = nullptr;
77 std::memset(s: error->sqlstate, c: '\0', n: sizeof(error->sqlstate));
78 error->vendor_code = -1;
79}
80
81AdbcStatusCode CheckResult(duckdb_state &res, AdbcError *error, const char *error_msg) {
82 if (!error) {
83 // Error should be a non-null pointer
84 return ADBC_STATUS_INVALID_ARGUMENT;
85 }
86 if (res != DuckDBSuccess) {
87 duckdb_adbc::SetError(error, message: error_msg);
88 return ADBC_STATUS_INTERNAL;
89 }
90 return ADBC_STATUS_OK;
91}
92
93AdbcStatusCode DatabaseNew(struct AdbcDatabase *database, struct AdbcError *error) {
94 auto status = SetErrorMaybe(result: database, error, error_message: "Missing database object");
95 if (status != ADBC_STATUS_OK) {
96 return status;
97 }
98 database->private_data = nullptr;
99 // you can't malloc a struct with a non-trivial C++ constructor
100 // and std::string has a non-trivial constructor. so we need
101 // to use new and delete rather than malloc and free.
102 auto wrapper = new DuckDBAdbcDatabaseWrapper;
103 status = SetErrorMaybe(result: wrapper, error, error_message: "Allocation error");
104 if (status != ADBC_STATUS_OK) {
105 return status;
106 }
107 database->private_data = wrapper;
108 auto res = duckdb_create_config(out_config: &wrapper->config);
109 return CheckResult(res, error, error_msg: "Failed to allocate");
110}
111
112AdbcStatusCode DatabaseSetOption(struct AdbcDatabase *database, const char *key, const char *value,
113 struct AdbcError *error) {
114 auto status = SetErrorMaybe(result: database, error, error_message: "Missing database object");
115 if (status != ADBC_STATUS_OK) {
116 return status;
117 }
118
119 status = SetErrorMaybe(result: key, error, error_message: "Missing key");
120 if (status != ADBC_STATUS_OK) {
121 return status;
122 }
123
124 auto wrapper = (DuckDBAdbcDatabaseWrapper *)database->private_data;
125 if (strcmp(s1: key, s2: "path") == 0) {
126 wrapper->path = value;
127 return ADBC_STATUS_OK;
128 }
129 auto res = duckdb_set_config(config: wrapper->config, name: key, option: value);
130
131 return CheckResult(res, error, error_msg: "Failed to set configuration option");
132}
133
134AdbcStatusCode DatabaseInit(struct AdbcDatabase *database, struct AdbcError *error) {
135 if (!error) {
136 return ADBC_STATUS_INVALID_ARGUMENT;
137 }
138 if (!database) {
139 duckdb_adbc::SetError(error, message: "ADBC Database has an invalid pointer");
140 return ADBC_STATUS_INVALID_ARGUMENT;
141 }
142 char *errormsg;
143 // TODO can we set the database path via option, too? Does not look like it...
144 auto wrapper = (DuckDBAdbcDatabaseWrapper *)database->private_data;
145 auto res = duckdb_open_ext(path: wrapper->path.c_str(), out_database: &wrapper->database, config: wrapper->config, out_error: &errormsg);
146 return CheckResult(res, error, error_msg: errormsg);
147}
148
149AdbcStatusCode DatabaseRelease(struct AdbcDatabase *database, struct AdbcError *error) {
150
151 if (database && database->private_data) {
152 auto wrapper = (DuckDBAdbcDatabaseWrapper *)database->private_data;
153
154 duckdb_close(database: &wrapper->database);
155 duckdb_destroy_config(config: &wrapper->config);
156 delete wrapper;
157 database->private_data = nullptr;
158 }
159 return ADBC_STATUS_OK;
160}
161
162AdbcStatusCode ConnectionNew(struct AdbcConnection *connection, struct AdbcError *error) {
163 auto status = SetErrorMaybe(result: connection, error, error_message: "Missing connection object");
164 if (status != ADBC_STATUS_OK) {
165 return status;
166 }
167
168 connection->private_data = nullptr;
169 return ADBC_STATUS_OK;
170}
171
172AdbcStatusCode ExecuteQuery(duckdb::Connection *conn, const char *query, struct AdbcError *error) {
173 auto res = conn->Query(query);
174 if (res->HasError()) {
175 auto error_message = "Failed to execute query \"" + std::string(query) + "\": " + res->GetError();
176 SetError(error, message: error_message);
177 return ADBC_STATUS_INTERNAL;
178 }
179 return ADBC_STATUS_OK;
180}
181
182AdbcStatusCode ConnectionSetOption(struct AdbcConnection *connection, const char *key, const char *value,
183 struct AdbcError *error) {
184 if (!connection) {
185 SetError(error, message: "Connection is not set");
186 return ADBC_STATUS_INVALID_ARGUMENT;
187 }
188 auto conn = (duckdb::Connection *)connection->private_data;
189 if (strcmp(s1: key, ADBC_CONNECTION_OPTION_AUTOCOMMIT) == 0) {
190 if (strcmp(s1: value, ADBC_OPTION_VALUE_ENABLED) == 0) {
191 if (conn->HasActiveTransaction()) {
192 AdbcStatusCode status = ExecuteQuery(conn, query: "COMMIT", error);
193 if (status != ADBC_STATUS_OK) {
194 return status;
195 }
196 } else {
197 // no-op
198 }
199 } else if (strcmp(s1: value, ADBC_OPTION_VALUE_DISABLED) == 0) {
200 if (conn->HasActiveTransaction()) {
201 // no-op
202 } else {
203 // begin
204 AdbcStatusCode status = ExecuteQuery(conn, query: "START TRANSACTION", error);
205 if (status != ADBC_STATUS_OK) {
206 return status;
207 }
208 }
209 } else {
210 auto error_message = "Invalid connection option value " + std::string(key) + "=" + std::string(value);
211 SetError(error, message: error_message);
212 return ADBC_STATUS_INVALID_ARGUMENT;
213 }
214 return ADBC_STATUS_OK;
215 }
216 auto error_message =
217 "Unknown connection option " + std::string(key) + "=" + (value ? std::string(value) : "(NULL)");
218 SetError(error, message: error_message);
219 return ADBC_STATUS_NOT_IMPLEMENTED;
220}
221
222AdbcStatusCode ConnectionReadPartition(struct AdbcConnection *connection, const uint8_t *serialized_partition,
223 size_t serialized_length, struct ArrowArrayStream *out,
224 struct AdbcError *error) {
225 SetError(error, message: "Read Partitions are not supported in DuckDB");
226 return ADBC_STATUS_NOT_IMPLEMENTED;
227}
228
229AdbcStatusCode StatementExecutePartitions(struct AdbcStatement *statement, struct ArrowSchema *schema,
230 struct AdbcPartitions *partitions, int64_t *rows_affected,
231 struct AdbcError *error) {
232 SetError(error, message: "Execute Partitions are not supported in DuckDB");
233 return ADBC_STATUS_NOT_IMPLEMENTED;
234}
235
236AdbcStatusCode ConnectionCommit(struct AdbcConnection *connection, struct AdbcError *error) {
237 if (!connection) {
238 SetError(error, message: "Connection is not set");
239 return ADBC_STATUS_INVALID_ARGUMENT;
240 }
241 auto conn = (duckdb::Connection *)connection->private_data;
242 if (!conn->HasActiveTransaction()) {
243 SetError(error, message: "No active transaction, cannot commit");
244 return ADBC_STATUS_INVALID_STATE;
245 }
246
247 AdbcStatusCode status = ExecuteQuery(conn, query: "COMMIT", error);
248 if (status != ADBC_STATUS_OK) {
249 return status;
250 }
251 return ExecuteQuery(conn, query: "START TRANSACTION", error);
252}
253
254AdbcStatusCode ConnectionRollback(struct AdbcConnection *connection, struct AdbcError *error) {
255 if (!connection) {
256 SetError(error, message: "Connection is not set");
257 return ADBC_STATUS_INVALID_ARGUMENT;
258 }
259 auto conn = (duckdb::Connection *)connection->private_data;
260 if (!conn->HasActiveTransaction()) {
261 SetError(error, message: "No active transaction, cannot rollback");
262 return ADBC_STATUS_INVALID_STATE;
263 }
264
265 AdbcStatusCode status = ExecuteQuery(conn, query: "ROLLBACK", error);
266 if (status != ADBC_STATUS_OK) {
267 return status;
268 }
269 return ExecuteQuery(conn, query: "START TRANSACTION", error);
270}
271
272AdbcStatusCode ConnectionInit(struct AdbcConnection *connection, struct AdbcDatabase *database,
273 struct AdbcError *error) {
274 auto status = SetErrorMaybe(result: database, error, error_message: "Missing database");
275 if (status != ADBC_STATUS_OK) {
276 return status;
277 }
278 status = SetErrorMaybe(result: database->private_data, error, error_message: "Invalid database");
279 if (status != ADBC_STATUS_OK) {
280 return status;
281 }
282 status = SetErrorMaybe(result: connection, error, error_message: "Missing connection");
283 if (status != ADBC_STATUS_OK) {
284 return status;
285 }
286 auto database_wrapper = (DuckDBAdbcDatabaseWrapper *)database->private_data;
287
288 connection->private_data = nullptr;
289 auto res = duckdb_connect(database: database_wrapper->database, out_connection: (duckdb_connection *)&connection->private_data);
290 return CheckResult(res, error, error_msg: "Failed to connect to Database");
291}
292
293AdbcStatusCode ConnectionRelease(struct AdbcConnection *connection, struct AdbcError *error) {
294 if (connection && connection->private_data) {
295 duckdb_disconnect(connection: (duckdb_connection *)&connection->private_data);
296 connection->private_data = nullptr;
297 }
298 return ADBC_STATUS_OK;
299}
300
301// some stream callbacks
302
303static int get_schema(struct ArrowArrayStream *stream, struct ArrowSchema *out) {
304 if (!stream || !stream->private_data || !out) {
305 return DuckDBError;
306 }
307 return duckdb_query_arrow_schema(result: (duckdb_arrow)stream->private_data, out_schema: (duckdb_arrow_schema *)&out);
308}
309
310static int get_next(struct ArrowArrayStream *stream, struct ArrowArray *out) {
311 if (!stream || !stream->private_data || !out) {
312 return DuckDBError;
313 }
314 out->release = nullptr;
315
316 return duckdb_query_arrow_array(result: (duckdb_arrow)stream->private_data, out_array: (duckdb_arrow_array *)&out);
317}
318
319void release(struct ArrowArrayStream *stream) {
320 if (!stream || !stream->release) {
321 return;
322 }
323 if (stream->private_data) {
324 duckdb_destroy_arrow(result: (duckdb_arrow *)&stream->private_data);
325 stream->private_data = nullptr;
326 }
327 stream->release = nullptr;
328}
329
330const char *get_last_error(struct ArrowArrayStream *stream) {
331 if (!stream) {
332 return nullptr;
333 }
334 return nullptr;
335 // return duckdb_query_arrow_error(stream);
336}
337
338// this is an evil hack, normally we would need a stream factory here, but its probably much easier if the adbc clients
339// just hand over a stream
340
341duckdb::unique_ptr<duckdb::ArrowArrayStreamWrapper>
342stream_produce(uintptr_t factory_ptr,
343 std::pair<std::unordered_map<idx_t, std::string>, std::vector<std::string>> &project_columns,
344 duckdb::TableFilterSet *filters) {
345
346 // TODO this will ignore any projections or filters but since we don't expose the scan it should be sort of fine
347 auto res = duckdb::make_uniq<duckdb::ArrowArrayStreamWrapper>();
348 res->arrow_array_stream = *(ArrowArrayStream *)factory_ptr;
349 return res;
350}
351
352void stream_schema(uintptr_t factory_ptr, duckdb::ArrowSchemaWrapper &schema) {
353 auto stream = (ArrowArrayStream *)factory_ptr;
354 get_schema(stream, out: &schema.arrow_schema);
355}
356
357AdbcStatusCode Ingest(duckdb_connection connection, const char *table_name, struct ArrowArrayStream *input,
358 struct AdbcError *error) {
359
360 auto status = SetErrorMaybe(result: connection, error, error_message: "Invalid connection");
361 if (status != ADBC_STATUS_OK) {
362 return status;
363 }
364
365 status = SetErrorMaybe(result: input, error, error_message: "Missing input arrow stream pointer");
366 if (status != ADBC_STATUS_OK) {
367 return status;
368 }
369
370 status = SetErrorMaybe(result: table_name, error, error_message: "Missing database object name");
371 if (status != ADBC_STATUS_OK) {
372 return status;
373 }
374 auto cconn = (duckdb::Connection *)connection;
375
376 auto has_table = cconn->TableInfo(table_name);
377 auto arrow_scan = cconn->TableFunction(tname: "arrow_scan", values: {duckdb::Value::POINTER(value: (uintptr_t)input),
378 duckdb::Value::POINTER(value: (uintptr_t)stream_produce),
379 duckdb::Value::POINTER(value: (uintptr_t)get_schema)});
380 try {
381 if (!has_table) {
382 // We create the table based on an Arrow Scanner
383 arrow_scan->Create(table_name);
384 } else {
385 arrow_scan->CreateView(name: "temp_adbc_view", replace: true, temporary: true);
386 auto query = "insert into " + std::string(table_name) + " select * from temp_adbc_view";
387 auto result = cconn->Query(query);
388 }
389 // After creating a table, the arrow array stream is released. Hence we must set it as released to avoid
390 // double-releasing it
391 input->release = nullptr;
392 } catch (std::exception &ex) {
393 if (error) {
394 error->message = strdup(s: ex.what());
395 }
396 return ADBC_STATUS_INTERNAL;
397 } catch (...) {
398 return ADBC_STATUS_INTERNAL;
399 }
400 return ADBC_STATUS_OK;
401}
402
403struct DuckDBAdbcStatementWrapper {
404 ::duckdb_connection connection;
405 ::duckdb_arrow result;
406 ::duckdb_prepared_statement statement;
407 char *ingestion_table_name;
408 ArrowArrayStream *ingestion_stream;
409};
410
411AdbcStatusCode StatementNew(struct AdbcConnection *connection, struct AdbcStatement *statement,
412 struct AdbcError *error) {
413
414 auto status = SetErrorMaybe(result: connection, error, error_message: "Missing connection object");
415 if (status != ADBC_STATUS_OK) {
416 return status;
417 }
418
419 status = SetErrorMaybe(result: connection->private_data, error, error_message: "Invalid connection object");
420 if (status != ADBC_STATUS_OK) {
421 return status;
422 }
423
424 status = SetErrorMaybe(result: statement, error, error_message: "Missing statement object");
425 if (status != ADBC_STATUS_OK) {
426 return status;
427 }
428
429 statement->private_data = nullptr;
430
431 auto statement_wrapper = (DuckDBAdbcStatementWrapper *)malloc(size: sizeof(DuckDBAdbcStatementWrapper));
432 status = SetErrorMaybe(result: statement_wrapper, error, error_message: "Allocation error");
433 if (status != ADBC_STATUS_OK) {
434 return status;
435 }
436
437 statement->private_data = statement_wrapper;
438 statement_wrapper->connection = (duckdb_connection)connection->private_data;
439 statement_wrapper->statement = nullptr;
440 statement_wrapper->result = nullptr;
441 statement_wrapper->ingestion_stream = nullptr;
442 statement_wrapper->ingestion_table_name = nullptr;
443 return ADBC_STATUS_OK;
444}
445
446AdbcStatusCode StatementRelease(struct AdbcStatement *statement, struct AdbcError *error) {
447
448 if (statement && statement->private_data) {
449 auto wrapper = (DuckDBAdbcStatementWrapper *)statement->private_data;
450 if (wrapper->statement) {
451 duckdb_destroy_prepare(prepared_statement: &wrapper->statement);
452 wrapper->statement = nullptr;
453 }
454 if (wrapper->result) {
455 duckdb_destroy_arrow(result: &wrapper->result);
456 wrapper->result = nullptr;
457 }
458 if (wrapper->ingestion_stream) {
459 wrapper->ingestion_stream->release(wrapper->ingestion_stream);
460 wrapper->ingestion_stream->release = nullptr;
461 wrapper->ingestion_stream = nullptr;
462 }
463 if (wrapper->ingestion_table_name) {
464 free(ptr: wrapper->ingestion_table_name);
465 wrapper->ingestion_table_name = nullptr;
466 }
467 free(ptr: statement->private_data);
468 statement->private_data = nullptr;
469 }
470 return ADBC_STATUS_OK;
471}
472
473AdbcStatusCode StatementExecuteQuery(struct AdbcStatement *statement, struct ArrowArrayStream *out,
474 int64_t *rows_affected, struct AdbcError *error) {
475 auto status = SetErrorMaybe(result: statement, error, error_message: "Missing statement object");
476 if (status != ADBC_STATUS_OK) {
477 return status;
478 }
479
480 status = SetErrorMaybe(result: statement->private_data, error, error_message: "Invalid statement object");
481 if (status != ADBC_STATUS_OK) {
482 return status;
483 }
484
485 auto wrapper = (DuckDBAdbcStatementWrapper *)statement->private_data;
486
487 // TODO: Set affected rows, careful with early return
488 if (rows_affected) {
489 *rows_affected = 0;
490 }
491
492 if (wrapper->ingestion_stream && wrapper->ingestion_table_name) {
493 auto stream = wrapper->ingestion_stream;
494 wrapper->ingestion_stream = nullptr;
495 return Ingest(connection: wrapper->connection, table_name: wrapper->ingestion_table_name, input: stream, error);
496 }
497
498 auto res = duckdb_execute_prepared_arrow(prepared_statement: wrapper->statement, out_result: &wrapper->result);
499 if (res != DuckDBSuccess) {
500 SetError(error, message: duckdb_query_arrow_error(result: wrapper->result));
501 return ADBC_STATUS_INVALID_ARGUMENT;
502 }
503
504 if (out) {
505 out->private_data = wrapper->result;
506 out->get_schema = get_schema;
507 out->get_next = get_next;
508 out->release = release;
509 out->get_last_error = get_last_error;
510
511 // because we handed out the stream pointer its no longer our responsibility to destroy it in
512 // AdbcStatementRelease, this is now done in release()
513 wrapper->result = nullptr;
514 }
515
516 return ADBC_STATUS_OK;
517}
518
519// this is a nop for us
520AdbcStatusCode StatementPrepare(struct AdbcStatement *statement, struct AdbcError *error) {
521 auto status = SetErrorMaybe(result: statement, error, error_message: "Missing statement object");
522 if (status != ADBC_STATUS_OK) {
523 return status;
524 }
525
526 status = SetErrorMaybe(result: statement->private_data, error, error_message: "Invalid statement object");
527 if (status != ADBC_STATUS_OK) {
528 return status;
529 }
530
531 return ADBC_STATUS_OK;
532}
533
534AdbcStatusCode StatementSetSqlQuery(struct AdbcStatement *statement, const char *query, struct AdbcError *error) {
535 auto status = SetErrorMaybe(result: statement, error, error_message: "Missing statement object");
536 if (status != ADBC_STATUS_OK) {
537 return status;
538 }
539 status = SetErrorMaybe(result: query, error, error_message: "Missing query");
540 if (status != ADBC_STATUS_OK) {
541 return status;
542 }
543
544 auto wrapper = (DuckDBAdbcStatementWrapper *)statement->private_data;
545 auto res = duckdb_prepare(connection: wrapper->connection, query, out_prepared_statement: &wrapper->statement);
546 auto error_msg = duckdb_prepare_error(prepared_statement: wrapper->statement);
547 return CheckResult(res, error, error_msg);
548}
549
550AdbcStatusCode StatementBindStream(struct AdbcStatement *statement, struct ArrowArrayStream *values,
551 struct AdbcError *error) {
552 auto status = SetErrorMaybe(result: statement, error, error_message: "Missing statement object");
553 if (status != ADBC_STATUS_OK) {
554 return status;
555 }
556 status = SetErrorMaybe(result: values, error, error_message: "Missing stream object");
557 if (status != ADBC_STATUS_OK) {
558 return status;
559 }
560 auto wrapper = (DuckDBAdbcStatementWrapper *)statement->private_data;
561 wrapper->ingestion_stream = values;
562 return ADBC_STATUS_OK;
563}
564
565AdbcStatusCode StatementSetOption(struct AdbcStatement *statement, const char *key, const char *value,
566 struct AdbcError *error) {
567 auto status = SetErrorMaybe(result: statement, error, error_message: "Missing statement object");
568 if (status != ADBC_STATUS_OK) {
569 return status;
570 }
571 status = SetErrorMaybe(result: key, error, error_message: "Missing key object");
572 if (status != ADBC_STATUS_OK) {
573 return status;
574 }
575 auto wrapper = (DuckDBAdbcStatementWrapper *)statement->private_data;
576
577 if (strcmp(s1: key, ADBC_INGEST_OPTION_TARGET_TABLE) == 0) {
578 wrapper->ingestion_table_name = strdup(s: value);
579 return ADBC_STATUS_OK;
580 }
581 return ADBC_STATUS_INVALID_ARGUMENT;
582}
583
584static AdbcStatusCode QueryInternal(struct AdbcConnection *connection, struct ArrowArrayStream *out, const char *query,
585 struct AdbcError *error) {
586 AdbcStatement statement;
587
588 auto status = StatementNew(connection, statement: &statement, error);
589 if (status != ADBC_STATUS_OK) {
590 SetError(error, message: "unable to initialize statement");
591 return status;
592 }
593 status = StatementSetSqlQuery(statement: &statement, query, error);
594 if (status != ADBC_STATUS_OK) {
595 SetError(error, message: "unable to initialize statement");
596 return status;
597 }
598 status = StatementExecuteQuery(statement: &statement, out, rows_affected: nullptr, error);
599 if (status != ADBC_STATUS_OK) {
600 SetError(error, message: "unable to initialize statement");
601 return status;
602 }
603
604 return ADBC_STATUS_OK;
605}
606
607AdbcStatusCode ConnectionGetObjects(struct AdbcConnection *connection, int depth, const char *catalog,
608 const char *db_schema, const char *table_name, const char **table_type,
609 const char *column_name, struct ArrowArrayStream *out, struct AdbcError *error) {
610 if (catalog != nullptr) {
611 if (strcmp(s1: catalog, s2: "duckdb") == 0) {
612 SetError(error, message: "catalog must be NULL or 'duckdb'");
613 return ADBC_STATUS_INVALID_ARGUMENT;
614 }
615 }
616
617 if (table_type != nullptr) {
618 SetError(error, message: "Table types parameter not yet supported");
619 return ADBC_STATUS_NOT_IMPLEMENTED;
620 }
621
622 auto q = duckdb::StringUtil::Format(fmt_str: R"(
623SELECT table_schema db_schema_name, LIST(table_schema_list) db_schema_tables FROM (
624 SELECT table_schema, { table_name : table_name, table_columns : LIST({column_name : column_name, ordinal_position : ordinal_position + 1, remarks : ''})} table_schema_list FROM information_schema.columns WHERE table_schema LIKE '%s' AND table_name LIKE '%s' AND column_name LIKE '%s' GROUP BY table_schema, table_name
625 ) GROUP BY table_schema;
626)",
627 params: db_schema ? db_schema : "%", params: table_name ? table_name : "%",
628 params: column_name ? column_name : "%");
629
630 return QueryInternal(connection, out, query: q.c_str(), error);
631}
632
633AdbcStatusCode ConnectionGetTableTypes(struct AdbcConnection *connection, struct ArrowArrayStream *out,
634 struct AdbcError *error) {
635 const char *q = "SELECT DISTINCT table_type FROM information_schema.tables ORDER BY table_type";
636 return QueryInternal(connection, out, query: q, error);
637}
638
639} // namespace duckdb_adbc
640