Skip to content

Commit 8585179

Browse files
Adds an initial implementation of http_head
1 parent a4b9652 commit 8585179

File tree

2 files changed

+84
-1
lines changed

2 files changed

+84
-1
lines changed

src/http_client_extension.cpp

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,61 @@ static int ConvertListEntryToMap(const list_entry_t& list_entry, const duckdb::V
156156
return result.size();
157157
}
158158

159+
160+
161+
std::string headers_to_string(const duckdb_httplib_openssl::Headers& headers) {
162+
std::string result = "{";
163+
164+
for (const auto& [key, value] : headers) {
165+
// Convert ci string to regular string by converting to lowercase in-place
166+
std::string lower_key = key;
167+
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(),
168+
[](unsigned char c){ return std::tolower(c); });
169+
170+
result += "\"" + escape_json(lower_key) + "\":\"" + escape_json(value) + "\",";
171+
}
172+
173+
if (result.length() > 1) {
174+
result.pop_back(); // Remove trailing comma
175+
}
176+
result += "}";
177+
178+
return result;
179+
}
180+
181+
182+
static void HTTPHeadRequestFunction(DataChunk &args, ExpressionState &state, Vector &result) {
183+
D_ASSERT(args.data.size() == 1);
184+
185+
UnaryExecutor::Execute<string_t, string_t>(args.data[0], result, args.size(), [&](string_t input) {
186+
std::string url = input.GetString();
187+
188+
// Use helper to setup client and parse URL
189+
auto client_and_path = SetupHttpClient(url);
190+
auto &client = client_and_path.first;
191+
auto &path = client_and_path.second;
192+
193+
// Make the GET request
194+
auto res = client.Head(path.c_str());
195+
auto headers = headers_to_string(res->headers);
196+
if (res) {
197+
std::string response = StringUtil::Format(
198+
"{ \"status\": %i, \"reason\": \"%s\", \"headers\": \"%s\" }",
199+
res->status,
200+
escape_json(res->reason),
201+
escape_json(headers)
202+
);
203+
return StringVector::AddString(result, response);
204+
} else {
205+
std::string response = StringUtil::Format(
206+
"{ \"status\": %i, \"reason\": \"%s\", \"headers\": \"%s\" }",
207+
-1, GetHttpErrorMessage(res, "HEAD"), ""
208+
);
209+
return StringVector::AddString(result, response);
210+
}
211+
});
212+
}
213+
159214
static void HTTPGetRequestFunction(DataChunk &args, ExpressionState &state, Vector &result) {
160215
D_ASSERT(args.data.size() == 1);
161216

@@ -303,6 +358,10 @@ static void HTTPPostFormRequestFunction(DataChunk &args, ExpressionState &state,
303358

304359

305360
static void LoadInternal(DatabaseInstance &instance) {
361+
ScalarFunctionSet http_head("http_head");
362+
http_head.AddFunction(ScalarFunction({LogicalType::VARCHAR}, LogicalType::JSON(), HTTPHeadRequestFunction));
363+
ExtensionUtil::RegisterFunction(instance, http_head);
364+
306365
ScalarFunctionSet http_get("http_get");
307366
http_get.AddFunction(ScalarFunction({LogicalType::VARCHAR}, LogicalType::JSON(), HTTPGetRequestFunction));
308367
http_get.AddFunction(ScalarFunction(
@@ -358,4 +417,3 @@ DUCKDB_EXTENSION_API const char *http_client_version() {
358417
#ifndef DUCKDB_EXTENSION_MAIN
359418
#error DUCKDB_EXTENSION_MAIN not defined
360419
#endif
361-

test/sql/httpclient.test

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,28 @@ FROM
199199
----
200200
200 OK 10
201201

202+
# Confirm the HEAD function works
203+
query III
204+
WITH __input AS (
205+
SELECT
206+
http_head(
207+
'https://httpbin.org/delay/0'
208+
) AS res
209+
),
210+
__response AS (
211+
SELECT
212+
(res->>'status')::INT AS status,
213+
(res->>'reason') AS reason,
214+
unnest( from_json(((res->>'headers')::JSON), '{"connection": "VARCHAR"}') ) AS features
215+
FROM
216+
__input
217+
)
218+
SELECT
219+
__response.status,
220+
__response.reason,
221+
__response.connection AS connection
222+
FROM
223+
__response
224+
;
225+
----
226+
200 OK close

0 commit comments

Comments
 (0)