|
12 | 12 | #include "duckdb/main/client_context.hpp" |
13 | 13 | #include "duckdb/main/database.hpp" |
14 | 14 | #include "duckdb/main/secret/secret_manager.hpp" |
| 15 | +#include "duckdb/storage/buffer_manager.hpp" |
15 | 16 | #include "http_state.hpp" |
16 | 17 |
|
17 | 18 | #include <chrono> |
@@ -59,6 +60,7 @@ HTTPParams HTTPParams::ReadFrom(optional_ptr<FileOpener> opener, optional_ptr<Fi |
59 | 60 | info); |
60 | 61 | FileOpener::TryGetCurrentSetting(opener, "ca_cert_file", result.ca_cert_file, info); |
61 | 62 | FileOpener::TryGetCurrentSetting(opener, "hf_max_per_page", result.hf_max_per_page, info); |
| 63 | + FileOpener::TryGetCurrentSetting(opener, "enable_http_write", result.enable_http_write, info); |
62 | 64 |
|
63 | 65 | // HTTP Secret lookups |
64 | 66 | KeyValueSecretReader settings_reader(*opener, info, "http"); |
@@ -576,7 +578,100 @@ int64_t HTTPFileSystem::Read(FileHandle &handle, void *buffer, int64_t nr_bytes) |
576 | 578 | } |
577 | 579 |
|
578 | 580 | void HTTPFileSystem::Write(FileHandle &handle, void *buffer, int64_t nr_bytes, idx_t location) { |
579 | | - throw NotImplementedException("Writing to HTTP files not implemented"); |
| 581 | + auto &hfh = handle.Cast<HTTPFileHandle>(); |
| 582 | + |
| 583 | + // Check if HTTP write is enabled |
| 584 | + if (!hfh.http_params.enable_http_write) { |
| 585 | + throw NotImplementedException("Writing to HTTP files not implemented"); |
| 586 | + } |
| 587 | + |
| 588 | + if (!buffer || nr_bytes <= 1) { |
| 589 | + return; |
| 590 | + } |
| 591 | + |
| 592 | + // Initialize the write buffer if it is not already done |
| 593 | + if (hfh.write_buffer.empty()) { |
| 594 | + hfh.write_buffer.resize(hfh.WRITE_BUFFER_LEN); |
| 595 | + hfh.write_buffer_idx = 0; |
| 596 | + } |
| 597 | + |
| 598 | + idx_t bytes_to_copy = nr_bytes; |
| 599 | + idx_t buffer_offset = 0; |
| 600 | + |
| 601 | + // Accumulate data into the write buffer |
| 602 | + while (bytes_to_copy > 0) { |
| 603 | + idx_t space_in_buffer = hfh.WRITE_BUFFER_LEN - hfh.write_buffer_idx; |
| 604 | + idx_t copy_amount = MinValue<idx_t>(space_in_buffer, bytes_to_copy); |
| 605 | + |
| 606 | + // Copy data to the write buffer |
| 607 | + memcpy(hfh.write_buffer.data() + hfh.write_buffer_idx, (char *)buffer + buffer_offset, copy_amount); |
| 608 | + hfh.write_buffer_idx += copy_amount; |
| 609 | + bytes_to_copy -= copy_amount; |
| 610 | + buffer_offset += copy_amount; |
| 611 | + |
| 612 | + // std::cout << "Write buffer idx after write: " << hfh.write_buffer_idx << std::endl; |
| 613 | + |
| 614 | + // If the buffer is full, send the data |
| 615 | + if (hfh.write_buffer_idx == hfh.WRITE_BUFFER_LEN) { |
| 616 | + // Perform the HTTP POST request |
| 617 | + FlushBuffer(hfh); |
| 618 | + } |
| 619 | + } |
| 620 | + |
| 621 | + // Update the file offset |
| 622 | + hfh.file_offset += nr_bytes; |
| 623 | + |
| 624 | + // std::cout << "Completed Write operation. Total bytes written: " << nr_bytes << std::endl; |
| 625 | +} |
| 626 | + |
| 627 | +void HTTPFileSystem::FlushBuffer(HTTPFileHandle &hfh) { |
| 628 | + // If no data in buffer, return |
| 629 | + if (hfh.write_buffer_idx <= 1) { |
| 630 | + return; |
| 631 | + } |
| 632 | + |
| 633 | + // Prepare the URL and headers for the HTTP POST request |
| 634 | + string path, proto_host_port; |
| 635 | + ParseUrl(hfh.path, path, proto_host_port); |
| 636 | + |
| 637 | + HeaderMap header_map; |
| 638 | + auto headers = InitializeHeaders(header_map, hfh.http_params); |
| 639 | + |
| 640 | + // Define the request lambda |
| 641 | + std::function<duckdb_httplib_openssl::Result(void)> request([&]() { |
| 642 | + auto client = GetClient(hfh.http_params, proto_host_port.c_str(), &hfh); |
| 643 | + duckdb_httplib_openssl::Request req; |
| 644 | + req.method = "POST"; |
| 645 | + req.path = path; |
| 646 | + req.headers = *headers; |
| 647 | + req.headers.emplace("Content-Type", "application/octet-stream"); |
| 648 | + |
| 649 | + // Prepare the request body from the write buffer |
| 650 | + req.body = std::string(reinterpret_cast<const char *>(hfh.write_buffer.data()), hfh.write_buffer_idx); |
| 651 | + |
| 652 | + // std::cout << "Sending request with " << hfh.write_buffer_idx << " bytes of data" << std::endl; |
| 653 | + |
| 654 | + return client->send(req); |
| 655 | + }); |
| 656 | + |
| 657 | + // Perform the HTTP POST request and handle retries |
| 658 | + auto response = RunRequestWithRetry(request, hfh.path, "POST", hfh.http_params); |
| 659 | + |
| 660 | + // Check if the response was successful (HTTP 200-299 status code) |
| 661 | + if (response->code < 200 || response->code >= 300) { |
| 662 | + throw HTTPException(*response, "HTTP POST request failed to '%s' with status code: %d", hfh.path.c_str(), |
| 663 | + response->code); |
| 664 | + } |
| 665 | + |
| 666 | + // Reset the write buffer index after sending data |
| 667 | + hfh.write_buffer_idx = 0; |
| 668 | +} |
| 669 | + |
| 670 | +void HTTPFileHandle::Close() { |
| 671 | + auto &fs = (HTTPFileSystem &)file_system; |
| 672 | + if (flags.OpenForWriting()) { |
| 673 | + fs.FlushBuffer(*this); |
| 674 | + } |
580 | 675 | } |
581 | 676 |
|
582 | 677 | int64_t HTTPFileSystem::Write(FileHandle &handle, void *buffer, int64_t nr_bytes) { |
@@ -829,5 +924,15 @@ ResponseWrapper::ResponseWrapper(duckdb_httplib_openssl::Response &res, string & |
829 | 924 | body = res.body; |
830 | 925 | } |
831 | 926 |
|
832 | | -HTTPFileHandle::~HTTPFileHandle() = default; |
| 927 | +HTTPFileHandle::~HTTPFileHandle() { |
| 928 | + if (Exception::UncaughtException()) { |
| 929 | + return; |
| 930 | + } |
| 931 | + |
| 932 | + try { |
| 933 | + Close(); |
| 934 | + } catch (...) { // NOLINT |
| 935 | + } |
| 936 | +} |
| 937 | + |
833 | 938 | } // namespace duckdb |
0 commit comments