duckdb 0.8.2-dev2842.0 → 0.8.2-dev3007.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/binding.gyp +1 -0
  2. package/package.json +1 -1
  3. package/src/duckdb/extension/json/include/json_deserializer.hpp +1 -1
  4. package/src/duckdb/extension/json/include/json_serializer.hpp +1 -1
  5. package/src/duckdb/extension/json/json_deserializer.cpp +7 -5
  6. package/src/duckdb/extension/json/json_serializer.cpp +2 -3
  7. package/src/duckdb/src/common/adbc/adbc.cpp +400 -145
  8. package/src/duckdb/src/common/adbc/driver_manager.cpp +79 -31
  9. package/src/duckdb/src/common/adbc/nanoarrow/allocator.cpp +57 -0
  10. package/src/duckdb/src/common/adbc/nanoarrow/metadata.cpp +121 -0
  11. package/src/duckdb/src/common/adbc/nanoarrow/schema.cpp +474 -0
  12. package/src/duckdb/src/common/adbc/nanoarrow/single_batch_array_stream.cpp +84 -0
  13. package/src/duckdb/src/common/arrow/arrow_converter.cpp +4 -2
  14. package/src/duckdb/src/common/multi_file_reader.cpp +6 -0
  15. package/src/duckdb/src/execution/window_executor.cpp +5 -8
  16. package/src/duckdb/src/function/table/version/pragma_version.cpp +2 -2
  17. package/src/duckdb/src/include/duckdb/common/adbc/adbc.h +1 -0
  18. package/src/duckdb/src/include/duckdb/common/adbc/adbc.hpp +3 -3
  19. package/src/duckdb/src/include/duckdb/common/adbc/single_batch_array_stream.hpp +16 -0
  20. package/src/duckdb/src/include/duckdb/common/arrow/arrow_appender.hpp +1 -2
  21. package/src/duckdb/src/include/duckdb/common/arrow/arrow_converter.hpp +0 -2
  22. package/src/duckdb/src/include/duckdb/common/arrow/nanoarrow/nanoarrow.h +462 -0
  23. package/src/duckdb/src/include/duckdb/common/arrow/nanoarrow/nanoarrow.hpp +14 -0
  24. package/src/duckdb/src/include/duckdb/common/types/data_chunk.hpp +0 -2
  25. package/src/duckdb/src/include/duckdb/main/chunk_scan_state.hpp +2 -4
  26. package/src/duckdb/src/include/duckdb.h +16 -0
  27. package/src/duckdb/src/main/capi/arrow-c.cpp +41 -0
  28. package/src/duckdb/src/main/capi/prepared-c.cpp +60 -30
  29. package/src/duckdb/src/main/chunk_scan_state.cpp +6 -0
  30. package/src/duckdb/src/main/client_context.cpp +1 -1
  31. package/src/duckdb/src/optimizer/topn_optimizer.cpp +7 -0
  32. package/src/duckdb/src/parser/transform/constraint/transform_constraint.cpp +55 -38
  33. package/src/duckdb/src/storage/compression/bitpacking.cpp +1 -1
  34. package/src/duckdb/ub_src_common_adbc_nanoarrow.cpp +8 -0
  35. package/src/duckdb_node.hpp +1 -0
  36. package/src/statement.cpp +1 -1
@@ -6,12 +6,14 @@
6
6
 
7
7
  #include "duckdb.h"
8
8
  #include "duckdb/common/arrow/arrow_wrapper.hpp"
9
- #include "duckdb/common/arrow/arrow.hpp"
9
+ #include "duckdb/common/arrow/nanoarrow/nanoarrow.hpp"
10
10
 
11
11
  #ifndef DUCKDB_AMALGAMATION
12
12
  #include "duckdb/main/connection.hpp"
13
13
  #endif
14
14
 
15
+ #include "duckdb/common/adbc/single_batch_array_stream.hpp"
16
+
15
17
  #include <string.h>
16
18
  #include <stdlib.h>
17
19
 
@@ -33,7 +35,7 @@ duckdb_adbc::AdbcStatusCode duckdb_adbc_init(size_t count, struct duckdb_adbc::A
33
35
  driver->ConnectionGetTableTypes = duckdb_adbc::ConnectionGetTableTypes;
34
36
  driver->StatementNew = duckdb_adbc::StatementNew;
35
37
  driver->StatementRelease = duckdb_adbc::StatementRelease;
36
- // driver->StatementBind = duckdb::adbc::StatementBind;
38
+ driver->StatementBind = duckdb_adbc::StatementBind;
37
39
  driver->StatementBindStream = duckdb_adbc::StatementBindStream;
38
40
  driver->StatementExecuteQuery = duckdb_adbc::StatementExecuteQuery;
39
41
  driver->StatementPrepare = duckdb_adbc::StatementPrepare;
@@ -44,9 +46,13 @@ duckdb_adbc::AdbcStatusCode duckdb_adbc_init(size_t count, struct duckdb_adbc::A
44
46
  driver->ConnectionRollback = duckdb_adbc::ConnectionRollback;
45
47
  driver->ConnectionReadPartition = duckdb_adbc::ConnectionReadPartition;
46
48
  driver->StatementExecutePartitions = duckdb_adbc::StatementExecutePartitions;
49
+ driver->ConnectionGetInfo = duckdb_adbc::ConnectionGetInfo;
50
+ driver->StatementGetParameterSchema = duckdb_adbc::StatementGetParameterSchema;
47
51
  driver->ConnectionGetTableSchema = duckdb_adbc::ConnectionGetTableSchema;
48
52
  driver->StatementSetSubstraitPlan = duckdb_adbc::StatementSetSubstraitPlan;
49
53
 
54
+ driver->ConnectionGetInfo = duckdb_adbc::ConnectionGetInfo;
55
+ driver->StatementGetParameterSchema = duckdb_adbc::StatementGetParameterSchema;
50
56
  return ADBC_STATUS_OK;
51
57
  }
52
58
 
@@ -58,19 +64,30 @@ struct DuckDBAdbcStatementWrapper {
58
64
  ::duckdb_arrow result;
59
65
  ::duckdb_prepared_statement statement;
60
66
  char *ingestion_table_name;
61
- ArrowArrayStream *ingestion_stream;
67
+ ArrowArrayStream ingestion_stream;
62
68
  IngestionMode ingestion_mode = IngestionMode::CREATE;
63
69
  };
70
+
64
71
  static AdbcStatusCode QueryInternal(struct AdbcConnection *connection, struct ArrowArrayStream *out, const char *query,
65
- struct AdbcError *error);
66
- AdbcStatusCode SetErrorMaybe(const void *result, AdbcError *error, const std::string &error_message) {
67
- if (!error) {
68
- return ADBC_STATUS_INVALID_ARGUMENT;
72
+ struct AdbcError *error) {
73
+ AdbcStatement statement;
74
+
75
+ auto status = StatementNew(connection, &statement, error);
76
+ if (status != ADBC_STATUS_OK) {
77
+ SetError(error, "unable to initialize statement");
78
+ return status;
69
79
  }
70
- if (!result) {
71
- SetError(error, error_message);
72
- return ADBC_STATUS_INVALID_ARGUMENT;
80
+ status = StatementSetSqlQuery(&statement, query, error);
81
+ if (status != ADBC_STATUS_OK) {
82
+ SetError(error, "unable to initialize statement");
83
+ return status;
73
84
  }
85
+ status = StatementExecuteQuery(&statement, out, nullptr, error);
86
+ if (status != ADBC_STATUS_OK) {
87
+ SetError(error, "unable to initialize statement");
88
+ return status;
89
+ }
90
+
74
91
  return ADBC_STATUS_OK;
75
92
  }
76
93
 
@@ -83,12 +100,20 @@ struct DuckDBAdbcDatabaseWrapper {
83
100
  std::string path;
84
101
  };
85
102
 
86
- void InitiliazeADBCError(AdbcError *error) {
103
+ static void EmptyErrorRelease(AdbcError *error) {
104
+ // The object is valid but doesn't contain any data that needs to be cleaned up
105
+ // Just set the release to nullptr to indicate that it's no longer valid.
106
+ error->release = nullptr;
107
+ return;
108
+ }
109
+
110
+ void InitializeADBCError(AdbcError *error) {
87
111
  if (!error) {
88
112
  return;
89
113
  }
90
114
  error->message = nullptr;
91
- error->release = nullptr;
115
+ // Don't set to nullptr, as that indicates that it's invalid
116
+ error->release = EmptyErrorRelease;
92
117
  std::memset(error->sqlstate, '\0', sizeof(error->sqlstate));
93
118
  error->vendor_code = -1;
94
119
  }
@@ -106,18 +131,18 @@ AdbcStatusCode CheckResult(duckdb_state &res, AdbcError *error, const char *erro
106
131
  }
107
132
 
108
133
  AdbcStatusCode DatabaseNew(struct AdbcDatabase *database, struct AdbcError *error) {
109
- auto status = SetErrorMaybe(database, error, "Missing database object");
110
- if (status != ADBC_STATUS_OK) {
111
- return status;
134
+ if (!database) {
135
+ SetError(error, "Missing database object");
136
+ return ADBC_STATUS_INVALID_ARGUMENT;
112
137
  }
113
138
  database->private_data = nullptr;
114
139
  // you can't malloc a struct with a non-trivial C++ constructor
115
140
  // and std::string has a non-trivial constructor. so we need
116
141
  // to use new and delete rather than malloc and free.
117
142
  auto wrapper = new DuckDBAdbcDatabaseWrapper;
118
- status = SetErrorMaybe(wrapper, error, "Allocation error");
119
- if (status != ADBC_STATUS_OK) {
120
- return status;
143
+ if (!wrapper) {
144
+ SetError(error, "Allocation error");
145
+ return ADBC_STATUS_INVALID_ARGUMENT;
121
146
  }
122
147
  database->private_data = wrapper;
123
148
  auto res = duckdb_create_config(&wrapper->config);
@@ -148,14 +173,13 @@ AdbcStatusCode StatementSetSubstraitPlan(struct AdbcStatement *statement, const
148
173
 
149
174
  AdbcStatusCode DatabaseSetOption(struct AdbcDatabase *database, const char *key, const char *value,
150
175
  struct AdbcError *error) {
151
- auto status = SetErrorMaybe(database, error, "Missing database object");
152
- if (status != ADBC_STATUS_OK) {
153
- return status;
176
+ if (!database) {
177
+ SetError(error, "Missing database object");
178
+ return ADBC_STATUS_INVALID_ARGUMENT;
154
179
  }
155
-
156
- status = SetErrorMaybe(key, error, "Missing key");
157
- if (status != ADBC_STATUS_OK) {
158
- return status;
180
+ if (!key) {
181
+ SetError(error, "Missing key");
182
+ return ADBC_STATUS_INVALID_ARGUMENT;
159
183
  }
160
184
 
161
185
  auto wrapper = (DuckDBAdbcDatabaseWrapper *)database->private_data;
@@ -236,9 +260,9 @@ AdbcStatusCode ConnectionGetTableSchema(struct AdbcConnection *connection, const
236
260
  }
237
261
 
238
262
  AdbcStatusCode ConnectionNew(struct AdbcConnection *connection, struct AdbcError *error) {
239
- auto status = SetErrorMaybe(connection, error, "Missing connection object");
240
- if (status != ADBC_STATUS_OK) {
241
- return status;
263
+ if (!connection) {
264
+ SetError(error, "Missing connection object");
265
+ return ADBC_STATUS_INVALID_ARGUMENT;
242
266
  }
243
267
 
244
268
  connection->private_data = nullptr;
@@ -345,19 +369,129 @@ AdbcStatusCode ConnectionRollback(struct AdbcConnection *connection, struct Adbc
345
369
  return ExecuteQuery(conn, "START TRANSACTION", error);
346
370
  }
347
371
 
372
+ enum class AdbcInfoCode : uint32_t {
373
+ VENDOR_NAME,
374
+ VENDOR_VERSION,
375
+ DRIVER_NAME,
376
+ DRIVER_VERSION,
377
+ DRIVER_ARROW_VERSION,
378
+ UNRECOGNIZED // always the last entry of the enum
379
+ };
380
+
381
+ static AdbcInfoCode ConvertToInfoCode(uint32_t info_code) {
382
+ switch (info_code) {
383
+ case 0:
384
+ return AdbcInfoCode::VENDOR_NAME;
385
+ case 1:
386
+ return AdbcInfoCode::VENDOR_VERSION;
387
+ case 2:
388
+ return AdbcInfoCode::DRIVER_NAME;
389
+ case 3:
390
+ return AdbcInfoCode::DRIVER_VERSION;
391
+ case 4:
392
+ return AdbcInfoCode::DRIVER_ARROW_VERSION;
393
+ default:
394
+ return AdbcInfoCode::UNRECOGNIZED;
395
+ }
396
+ }
397
+
398
+ AdbcStatusCode ConnectionGetInfo(struct AdbcConnection *connection, uint32_t *info_codes, size_t info_codes_length,
399
+ struct ArrowArrayStream *out, struct AdbcError *error) {
400
+ if (!connection) {
401
+ SetError(error, "Missing connection object");
402
+ return ADBC_STATUS_INVALID_ARGUMENT;
403
+ }
404
+ if (!connection->private_data) {
405
+ SetError(error, "Connection is invalid");
406
+ return ADBC_STATUS_INVALID_ARGUMENT;
407
+ }
408
+ if (!out) {
409
+ SetError(error, "Output parameter was not provided");
410
+ return ADBC_STATUS_INVALID_ARGUMENT;
411
+ }
412
+
413
+ // If 'info_codes' is NULL, we should output all the info codes we recognize
414
+ size_t length = info_codes ? info_codes_length : (size_t)AdbcInfoCode::UNRECOGNIZED;
415
+
416
+ duckdb::string q = R"EOF(
417
+ select
418
+ name::UINTEGER as info_name,
419
+ info::UNION(
420
+ string_value VARCHAR,
421
+ bool_value BOOL,
422
+ int64_value BIGINT,
423
+ int32_bitmask INTEGER,
424
+ string_list VARCHAR[],
425
+ int32_to_int32_list_map MAP(INTEGER, INTEGER[])
426
+ ) as info_value from values
427
+ )EOF";
428
+
429
+ duckdb::string results = "";
430
+
431
+ for (size_t i = 0; i < length; i++) {
432
+ uint32_t code = info_codes ? info_codes[i] : i;
433
+ auto info_code = ConvertToInfoCode(code);
434
+ switch (info_code) {
435
+ case AdbcInfoCode::VENDOR_NAME: {
436
+ results += "(0, 'duckdb'),";
437
+ break;
438
+ }
439
+ case AdbcInfoCode::VENDOR_VERSION: {
440
+ results += duckdb::StringUtil::Format("(1, '%s'),", duckdb_library_version());
441
+ break;
442
+ }
443
+ case AdbcInfoCode::DRIVER_NAME: {
444
+ results += "(2, 'ADBC DuckDB Driver'),";
445
+ break;
446
+ }
447
+ case AdbcInfoCode::DRIVER_VERSION: {
448
+ // TODO: fill in driver version
449
+ results += "(3, '(unknown)'),";
450
+ break;
451
+ }
452
+ case AdbcInfoCode::DRIVER_ARROW_VERSION: {
453
+ // TODO: fill in arrow version
454
+ results += "(4, '(unknown)'),";
455
+ break;
456
+ }
457
+ case AdbcInfoCode::UNRECOGNIZED: {
458
+ // Unrecognized codes are not an error, just ignored
459
+ continue;
460
+ }
461
+ default: {
462
+ // Codes that we have implemented but not handled here are a developer error
463
+ SetError(error, "Info code recognized but not handled");
464
+ return ADBC_STATUS_INTERNAL;
465
+ }
466
+ }
467
+ }
468
+ if (results.empty()) {
469
+ // Add a group of values so the query parses
470
+ q += "(NULL, NULL)";
471
+ } else {
472
+ q += results;
473
+ }
474
+ q += " tbl(name, info)";
475
+ if (results.empty()) {
476
+ // Add an impossible where clause to return an empty result set
477
+ q += " where true = false";
478
+ }
479
+ return QueryInternal(connection, out, q.c_str(), error);
480
+ }
481
+
348
482
  AdbcStatusCode ConnectionInit(struct AdbcConnection *connection, struct AdbcDatabase *database,
349
483
  struct AdbcError *error) {
350
- auto status = SetErrorMaybe(database, error, "Missing database");
351
- if (status != ADBC_STATUS_OK) {
352
- return status;
484
+ if (!database) {
485
+ SetError(error, "Missing database object");
486
+ return ADBC_STATUS_INVALID_ARGUMENT;
353
487
  }
354
- status = SetErrorMaybe(database->private_data, error, "Invalid database");
355
- if (status != ADBC_STATUS_OK) {
356
- return status;
488
+ if (!database->private_data) {
489
+ SetError(error, "Invalid database");
490
+ return ADBC_STATUS_INVALID_ARGUMENT;
357
491
  }
358
- status = SetErrorMaybe(connection, error, "Missing connection");
359
- if (status != ADBC_STATUS_OK) {
360
- return status;
492
+ if (!connection) {
493
+ SetError(error, "Missing connection object");
494
+ return ADBC_STATUS_INVALID_ARGUMENT;
361
495
  }
362
496
  auto database_wrapper = (DuckDBAdbcDatabaseWrapper *)database->private_data;
363
497
 
@@ -433,20 +567,19 @@ void stream_schema(uintptr_t factory_ptr, duckdb::ArrowSchemaWrapper &schema) {
433
567
  AdbcStatusCode Ingest(duckdb_connection connection, const char *table_name, struct ArrowArrayStream *input,
434
568
  struct AdbcError *error, IngestionMode ingestion_mode) {
435
569
 
436
- auto status = SetErrorMaybe(connection, error, "Invalid connection");
437
- if (status != ADBC_STATUS_OK) {
438
- return status;
570
+ if (!connection) {
571
+ SetError(error, "Missing connection object");
572
+ return ADBC_STATUS_INVALID_ARGUMENT;
439
573
  }
440
-
441
- status = SetErrorMaybe(input, error, "Missing input arrow stream pointer");
442
- if (status != ADBC_STATUS_OK) {
443
- return status;
574
+ if (!input) {
575
+ SetError(error, "Missing input arrow stream pointer");
576
+ return ADBC_STATUS_INVALID_ARGUMENT;
444
577
  }
445
-
446
- status = SetErrorMaybe(table_name, error, "Missing database object name");
447
- if (status != ADBC_STATUS_OK) {
448
- return status;
578
+ if (!table_name) {
579
+ SetError(error, "Missing database object name");
580
+ return ADBC_STATUS_INVALID_ARGUMENT;
449
581
  }
582
+
450
583
  auto cconn = (duckdb::Connection *)connection;
451
584
 
452
585
  auto arrow_scan = cconn->TableFunction("arrow_scan", {duckdb::Value::POINTER((uintptr_t)input),
@@ -458,7 +591,7 @@ AdbcStatusCode Ingest(duckdb_connection connection, const char *table_name, stru
458
591
  arrow_scan->Create(table_name);
459
592
  } else {
460
593
  arrow_scan->CreateView("temp_adbc_view", true, true);
461
- auto query = "insert into " + std::string(table_name) + " select * from temp_adbc_view";
594
+ auto query = duckdb::StringUtil::Format("insert into \"%s\" select * from temp_adbc_view", table_name);
462
595
  auto result = cconn->Query(query);
463
596
  }
464
597
  // After creating a table, the arrow array stream is released. Hence we must set it as released to avoid
@@ -477,79 +610,134 @@ AdbcStatusCode Ingest(duckdb_connection connection, const char *table_name, stru
477
610
 
478
611
  AdbcStatusCode StatementNew(struct AdbcConnection *connection, struct AdbcStatement *statement,
479
612
  struct AdbcError *error) {
480
-
481
- auto status = SetErrorMaybe(connection, error, "Missing connection object");
482
- if (status != ADBC_STATUS_OK) {
483
- return status;
613
+ if (!connection) {
614
+ SetError(error, "Missing connection object");
615
+ return ADBC_STATUS_INVALID_ARGUMENT;
484
616
  }
485
-
486
- status = SetErrorMaybe(connection->private_data, error, "Invalid connection object");
487
- if (status != ADBC_STATUS_OK) {
488
- return status;
617
+ if (!connection->private_data) {
618
+ SetError(error, "Invalid connection object");
619
+ return ADBC_STATUS_INVALID_ARGUMENT;
489
620
  }
490
-
491
- status = SetErrorMaybe(statement, error, "Missing statement object");
492
- if (status != ADBC_STATUS_OK) {
493
- return status;
621
+ if (!statement) {
622
+ SetError(error, "Missing statement object");
623
+ return ADBC_STATUS_INVALID_ARGUMENT;
494
624
  }
495
625
 
496
626
  statement->private_data = nullptr;
497
627
 
498
628
  auto statement_wrapper = (DuckDBAdbcStatementWrapper *)malloc(sizeof(DuckDBAdbcStatementWrapper));
499
- status = SetErrorMaybe(statement_wrapper, error, "Allocation error");
500
- if (status != ADBC_STATUS_OK) {
501
- return status;
629
+ if (!statement_wrapper) {
630
+ SetError(error, "Allocation error");
631
+ return ADBC_STATUS_INVALID_ARGUMENT;
502
632
  }
503
633
 
504
634
  statement->private_data = statement_wrapper;
505
635
  statement_wrapper->connection = (duckdb_connection)connection->private_data;
506
636
  statement_wrapper->statement = nullptr;
507
637
  statement_wrapper->result = nullptr;
508
- statement_wrapper->ingestion_stream = nullptr;
638
+ statement_wrapper->ingestion_stream.release = nullptr;
509
639
  statement_wrapper->ingestion_table_name = nullptr;
510
640
  statement_wrapper->ingestion_mode = IngestionMode::CREATE;
511
641
  return ADBC_STATUS_OK;
512
642
  }
513
643
 
514
644
  AdbcStatusCode StatementRelease(struct AdbcStatement *statement, struct AdbcError *error) {
645
+ if (!statement || !statement->private_data) {
646
+ return ADBC_STATUS_OK;
647
+ }
648
+ auto wrapper = (DuckDBAdbcStatementWrapper *)statement->private_data;
649
+ if (wrapper->statement) {
650
+ duckdb_destroy_prepare(&wrapper->statement);
651
+ wrapper->statement = nullptr;
652
+ }
653
+ if (wrapper->result) {
654
+ duckdb_destroy_arrow(&wrapper->result);
655
+ wrapper->result = nullptr;
656
+ }
657
+ if (wrapper->ingestion_stream.release) {
658
+ wrapper->ingestion_stream.release(&wrapper->ingestion_stream);
659
+ wrapper->ingestion_stream.release = nullptr;
660
+ }
661
+ if (wrapper->ingestion_table_name) {
662
+ free(wrapper->ingestion_table_name);
663
+ wrapper->ingestion_table_name = nullptr;
664
+ }
665
+ free(statement->private_data);
666
+ statement->private_data = nullptr;
667
+ return ADBC_STATUS_OK;
668
+ }
515
669
 
516
- if (statement && statement->private_data) {
517
- auto wrapper = (DuckDBAdbcStatementWrapper *)statement->private_data;
518
- if (wrapper->statement) {
519
- duckdb_destroy_prepare(&wrapper->statement);
520
- wrapper->statement = nullptr;
521
- }
522
- if (wrapper->result) {
523
- duckdb_destroy_arrow(&wrapper->result);
524
- wrapper->result = nullptr;
525
- }
526
- if (wrapper->ingestion_stream) {
527
- wrapper->ingestion_stream->release(wrapper->ingestion_stream);
528
- wrapper->ingestion_stream->release = nullptr;
529
- wrapper->ingestion_stream = nullptr;
530
- }
531
- if (wrapper->ingestion_table_name) {
532
- free(wrapper->ingestion_table_name);
533
- wrapper->ingestion_table_name = nullptr;
670
+ AdbcStatusCode StatementGetParameterSchema(struct AdbcStatement *statement, struct ArrowSchema *schema,
671
+ struct AdbcError *error) {
672
+ if (!statement) {
673
+ SetError(error, "Missing statement object");
674
+ return ADBC_STATUS_INVALID_ARGUMENT;
675
+ }
676
+ if (!statement->private_data) {
677
+ SetError(error, "Invalid statement object");
678
+ return ADBC_STATUS_INVALID_ARGUMENT;
679
+ }
680
+ if (!schema) {
681
+ SetError(error, "Missing schema object");
682
+ return ADBC_STATUS_INVALID_ARGUMENT;
683
+ }
684
+ auto wrapper = (DuckDBAdbcStatementWrapper *)statement->private_data;
685
+ // TODO: we might want to cache this, but then we need to return a deep copy anyways.., so I'm not sure if that
686
+ // would be worth the extra management
687
+ auto res = duckdb_prepared_arrow_schema(wrapper->statement, (duckdb_arrow_schema *)&schema);
688
+ if (res != DuckDBSuccess) {
689
+ return ADBC_STATUS_INVALID_ARGUMENT;
690
+ }
691
+ return ADBC_STATUS_OK;
692
+ }
693
+
694
+ AdbcStatusCode GetPreparedParameters(duckdb_connection connection, duckdb::unique_ptr<duckdb::QueryResult> &result,
695
+ ArrowArrayStream *input, AdbcError *error) {
696
+
697
+ auto cconn = (duckdb::Connection *)connection;
698
+
699
+ try {
700
+ auto arrow_scan = cconn->TableFunction("arrow_scan", {duckdb::Value::POINTER((uintptr_t)input),
701
+ duckdb::Value::POINTER((uintptr_t)stream_produce),
702
+ duckdb::Value::POINTER((uintptr_t)input->get_schema)});
703
+ result = arrow_scan->Execute();
704
+ // After creating a table, the arrow array stream is released. Hence we must set it as released to avoid
705
+ // double-releasing it
706
+ input->release = nullptr;
707
+ } catch (std::exception &ex) {
708
+ if (error) {
709
+ error->message = strdup(ex.what());
534
710
  }
535
- free(statement->private_data);
536
- statement->private_data = nullptr;
711
+ return ADBC_STATUS_INTERNAL;
712
+ } catch (...) {
713
+ return ADBC_STATUS_INTERNAL;
537
714
  }
538
715
  return ADBC_STATUS_OK;
539
716
  }
540
717
 
718
+ static AdbcStatusCode IngestToTableFromBoundStream(DuckDBAdbcStatementWrapper *statement, AdbcError *error) {
719
+ // See ADBC_INGEST_OPTION_TARGET_TABLE
720
+ D_ASSERT(statement->ingestion_stream.release);
721
+ D_ASSERT(statement->ingestion_table_name);
722
+
723
+ // Take the input stream from the statement
724
+ auto stream = statement->ingestion_stream;
725
+ statement->ingestion_stream.release = nullptr;
726
+
727
+ // Ingest into a table from the bound stream
728
+ return Ingest(statement->connection, statement->ingestion_table_name, &stream, error, statement->ingestion_mode);
729
+ }
730
+
541
731
  AdbcStatusCode StatementExecuteQuery(struct AdbcStatement *statement, struct ArrowArrayStream *out,
542
732
  int64_t *rows_affected, struct AdbcError *error) {
543
- auto status = SetErrorMaybe(statement, error, "Missing statement object");
544
- if (status != ADBC_STATUS_OK) {
545
- return status;
733
+ if (!statement) {
734
+ SetError(error, "Missing statement object");
735
+ return ADBC_STATUS_INVALID_ARGUMENT;
546
736
  }
547
-
548
- status = SetErrorMaybe(statement->private_data, error, "Invalid statement object");
549
- if (status != ADBC_STATUS_OK) {
550
- return status;
737
+ if (!statement->private_data) {
738
+ SetError(error, "Invalid statement object");
739
+ return ADBC_STATUS_INVALID_ARGUMENT;
551
740
  }
552
-
553
741
  auto wrapper = (DuckDBAdbcStatementWrapper *)statement->private_data;
554
742
 
555
743
  // TODO: Set affected rows, careful with early return
@@ -557,16 +745,59 @@ AdbcStatusCode StatementExecuteQuery(struct AdbcStatement *statement, struct Arr
557
745
  *rows_affected = 0;
558
746
  }
559
747
 
560
- if (wrapper->ingestion_stream && wrapper->ingestion_table_name) {
561
- auto stream = wrapper->ingestion_stream;
562
- wrapper->ingestion_stream = nullptr;
563
- return Ingest(wrapper->connection, wrapper->ingestion_table_name, stream, error, wrapper->ingestion_mode);
748
+ const auto has_stream = wrapper->ingestion_stream.release != nullptr;
749
+ const auto to_table = wrapper->ingestion_table_name != nullptr;
750
+
751
+ if (has_stream && to_table) {
752
+ return IngestToTableFromBoundStream(wrapper, error);
564
753
  }
565
754
 
566
- auto res = duckdb_execute_prepared_arrow(wrapper->statement, &wrapper->result);
567
- if (res != DuckDBSuccess) {
568
- SetError(error, duckdb_query_arrow_error(wrapper->result));
569
- return ADBC_STATUS_INVALID_ARGUMENT;
755
+ if (has_stream) {
756
+ // A stream was bound to the statement, use that to bind parameters
757
+ duckdb::unique_ptr<duckdb::QueryResult> result;
758
+ ArrowArrayStream stream = wrapper->ingestion_stream;
759
+ wrapper->ingestion_stream.release = nullptr;
760
+ auto adbc_res = GetPreparedParameters(wrapper->connection, result, &stream, error);
761
+ if (adbc_res != ADBC_STATUS_OK) {
762
+ return adbc_res;
763
+ }
764
+ if (!result) {
765
+ return ADBC_STATUS_INVALID_ARGUMENT;
766
+ }
767
+ duckdb::unique_ptr<duckdb::DataChunk> chunk;
768
+ while ((chunk = result->Fetch()) != nullptr) {
769
+ if (chunk->size() == 0) {
770
+ SetError(error, "Please provide a non-empty chunk to be bound");
771
+ return ADBC_STATUS_INVALID_ARGUMENT;
772
+ }
773
+ if (chunk->size() != 1) {
774
+ // TODO: add support for binding multiple rows
775
+ SetError(error, "Binding multiple rows at once is not supported yet");
776
+ return ADBC_STATUS_NOT_IMPLEMENTED;
777
+ }
778
+ duckdb_clear_bindings(wrapper->statement);
779
+ for (idx_t col_idx = 0; col_idx < chunk->ColumnCount(); col_idx++) {
780
+ auto val = chunk->GetValue(col_idx, 0);
781
+ auto duck_val = (duckdb_value)&val;
782
+ auto res = duckdb_bind_value(wrapper->statement, 1 + col_idx, duck_val);
783
+ if (res != DuckDBSuccess) {
784
+ SetError(error, duckdb_prepare_error(wrapper->statement));
785
+ return ADBC_STATUS_INVALID_ARGUMENT;
786
+ }
787
+ }
788
+
789
+ auto res = duckdb_execute_prepared_arrow(wrapper->statement, &wrapper->result);
790
+ if (res != DuckDBSuccess) {
791
+ SetError(error, duckdb_query_arrow_error(wrapper->result));
792
+ return ADBC_STATUS_INVALID_ARGUMENT;
793
+ }
794
+ }
795
+ } else {
796
+ auto res = duckdb_execute_prepared_arrow(wrapper->statement, &wrapper->result);
797
+ if (res != DuckDBSuccess) {
798
+ SetError(error, duckdb_query_arrow_error(wrapper->result));
799
+ return ADBC_STATUS_INVALID_ARGUMENT;
800
+ }
570
801
  }
571
802
 
572
803
  if (out) {
@@ -590,21 +821,25 @@ AdbcStatusCode StatementPrepare(struct AdbcStatement *statement, struct AdbcErro
590
821
  SetError(error, "Missing statement object");
591
822
  return ADBC_STATUS_INVALID_ARGUMENT;
592
823
  }
593
- if (!error) {
594
- SetError(error, "Missing error object");
824
+ if (!statement->private_data) {
825
+ SetError(error, "Invalid statement object");
595
826
  return ADBC_STATUS_INVALID_ARGUMENT;
596
827
  }
597
828
  return ADBC_STATUS_OK;
598
829
  }
599
830
 
600
831
  AdbcStatusCode StatementSetSqlQuery(struct AdbcStatement *statement, const char *query, struct AdbcError *error) {
601
- auto status = SetErrorMaybe(statement, error, "Missing statement object");
602
- if (status != ADBC_STATUS_OK) {
603
- return status;
832
+ if (!statement) {
833
+ SetError(error, "Missing statement object");
834
+ return ADBC_STATUS_INVALID_ARGUMENT;
604
835
  }
605
- status = SetErrorMaybe(query, error, "Missing query");
606
- if (status != ADBC_STATUS_OK) {
607
- return status;
836
+ if (!statement->private_data) {
837
+ SetError(error, "Invalid statement object");
838
+ return ADBC_STATUS_INVALID_ARGUMENT;
839
+ }
840
+ if (!query) {
841
+ SetError(error, "Missing query");
842
+ return ADBC_STATUS_INVALID_ARGUMENT;
608
843
  }
609
844
 
610
845
  auto wrapper = (DuckDBAdbcStatementWrapper *)statement->private_data;
@@ -613,31 +848,74 @@ AdbcStatusCode StatementSetSqlQuery(struct AdbcStatement *statement, const char
613
848
  return CheckResult(res, error, error_msg);
614
849
  }
615
850
 
851
+ AdbcStatusCode StatementBind(struct AdbcStatement *statement, struct ArrowArray *values, struct ArrowSchema *schemas,
852
+ struct AdbcError *error) {
853
+ if (!statement) {
854
+ SetError(error, "Missing statement object");
855
+ return ADBC_STATUS_INVALID_ARGUMENT;
856
+ }
857
+ if (!statement->private_data) {
858
+ SetError(error, "Invalid statement object");
859
+ return ADBC_STATUS_INVALID_ARGUMENT;
860
+ }
861
+ if (!values) {
862
+ SetError(error, "Missing values object");
863
+ return ADBC_STATUS_INVALID_ARGUMENT;
864
+ }
865
+ if (!schemas) {
866
+ SetError(error, "Invalid schemas object");
867
+ return ADBC_STATUS_INVALID_ARGUMENT;
868
+ }
869
+
870
+ auto wrapper = (DuckDBAdbcStatementWrapper *)statement->private_data;
871
+ if (wrapper->ingestion_stream.release) {
872
+ // Free the stream that was previously bound
873
+ wrapper->ingestion_stream.release(&wrapper->ingestion_stream);
874
+ }
875
+ auto status = BatchToArrayStream(values, schemas, &wrapper->ingestion_stream, error);
876
+ return status;
877
+ }
878
+
616
879
  AdbcStatusCode StatementBindStream(struct AdbcStatement *statement, struct ArrowArrayStream *values,
617
880
  struct AdbcError *error) {
618
- auto status = SetErrorMaybe(statement, error, "Missing statement object");
619
- if (status != ADBC_STATUS_OK) {
620
- return status;
881
+ if (!statement) {
882
+ SetError(error, "Missing statement object");
883
+ return ADBC_STATUS_INVALID_ARGUMENT;
621
884
  }
622
- status = SetErrorMaybe(values, error, "Missing stream object");
623
- if (status != ADBC_STATUS_OK) {
624
- return status;
885
+ if (!statement->private_data) {
886
+ SetError(error, "Invalid statement object");
887
+ return ADBC_STATUS_INVALID_ARGUMENT;
888
+ }
889
+ if (!values) {
890
+ SetError(error, "Missing values object");
891
+ return ADBC_STATUS_INVALID_ARGUMENT;
625
892
  }
893
+
626
894
  auto wrapper = (DuckDBAdbcStatementWrapper *)statement->private_data;
627
- wrapper->ingestion_stream = values;
895
+ if (wrapper->ingestion_stream.release) {
896
+ // Release any resources currently held by the ingestion stream before we overwrite it
897
+ wrapper->ingestion_stream.release(&wrapper->ingestion_stream);
898
+ }
899
+ wrapper->ingestion_stream = *values;
900
+ values->release = nullptr;
628
901
  return ADBC_STATUS_OK;
629
902
  }
630
903
 
631
904
  AdbcStatusCode StatementSetOption(struct AdbcStatement *statement, const char *key, const char *value,
632
905
  struct AdbcError *error) {
633
- auto status = SetErrorMaybe(statement, error, "Missing statement object");
634
- if (status != ADBC_STATUS_OK) {
635
- return status;
906
+ if (!statement) {
907
+ SetError(error, "Missing statement object");
908
+ return ADBC_STATUS_INVALID_ARGUMENT;
636
909
  }
637
- status = SetErrorMaybe(key, error, "Missing key object");
638
- if (status != ADBC_STATUS_OK) {
639
- return status;
910
+ if (!statement->private_data) {
911
+ SetError(error, "Invalid statement object");
912
+ return ADBC_STATUS_INVALID_ARGUMENT;
913
+ }
914
+ if (!key) {
915
+ SetError(error, "Missing key object");
916
+ return ADBC_STATUS_INVALID_ARGUMENT;
640
917
  }
918
+
641
919
  auto wrapper = (DuckDBAdbcStatementWrapper *)statement->private_data;
642
920
 
643
921
  if (strcmp(key, ADBC_INGEST_OPTION_TARGET_TABLE) == 0) {
@@ -659,29 +937,6 @@ AdbcStatusCode StatementSetOption(struct AdbcStatement *statement, const char *k
659
937
  return ADBC_STATUS_INVALID_ARGUMENT;
660
938
  }
661
939
 
662
- AdbcStatusCode QueryInternal(struct AdbcConnection *connection, struct ArrowArrayStream *out, const char *query,
663
- struct AdbcError *error) {
664
- AdbcStatement statement;
665
-
666
- auto status = StatementNew(connection, &statement, error);
667
- if (status != ADBC_STATUS_OK) {
668
- SetError(error, "unable to initialize statement");
669
- return status;
670
- }
671
- status = StatementSetSqlQuery(&statement, query, error);
672
- if (status != ADBC_STATUS_OK) {
673
- SetError(error, "unable to initialize statement");
674
- return status;
675
- }
676
- status = StatementExecuteQuery(&statement, out, nullptr, error);
677
- if (status != ADBC_STATUS_OK) {
678
- SetError(error, "unable to initialize statement");
679
- return status;
680
- }
681
-
682
- return ADBC_STATUS_OK;
683
- }
684
-
685
940
  AdbcStatusCode ConnectionGetObjects(struct AdbcConnection *connection, int depth, const char *catalog,
686
941
  const char *db_schema, const char *table_name, const char **table_type,
687
942
  const char *column_name, struct ArrowArrayStream *out, struct AdbcError *error) {