Skip to content

Commit e0f38c6

Browse files
authored
Merge pull request #4 from ahuarte47/main_return-response
Funtions return response data as a JSON instead of the body
2 parents b143be7 + 7ac8ec9 commit e0f38c6

File tree

3 files changed

+158
-120
lines changed

3 files changed

+158
-120
lines changed

docs/README.md

+49-43
Original file line numberDiff line numberDiff line change
@@ -19,62 +19,68 @@ LOAD http_client;
1919
#### GET
2020
```sql
2121
D WITH __input AS (
22-
SELECT
23-
http_get(
22+
SELECT
23+
http_get(
2424
'https://httpbin.org/delay/0'
25-
) AS data
26-
),
27-
__features AS (
28-
SELECT
29-
unnest( from_json((data::JSON)->'headers', '{"Host": "VARCHAR"}') )
30-
AS features
31-
FROM
32-
__input
33-
)
25+
) AS res
26+
),
27+
__response AS (
3428
SELECT
35-
__features.Host AS host,
29+
(res->>'status')::INT AS status,
30+
(res->>'reason') AS reason,
31+
unnest( from_json(((res->>'body')::JSON)->'headers', '{"Host": "VARCHAR"}') ) AS features
3632
FROM
37-
__features
38-
;
39-
┌─────────────┐
40-
│ host │
41-
varchar
42-
├─────────────┤
43-
httpbin.org
44-
└─────────────┘
33+
__input
34+
)
35+
SELECT
36+
__response.status,
37+
__response.reason,
38+
__response.Host AS host,
39+
FROM
40+
__response
41+
;
42+
┌────────┬─────────┬─────────────┐
43+
│ status │ reason │ host │
44+
│ int32 │ varcharvarchar
45+
├────────┼─────────┼─────────────┤
46+
200 │ OK │ httpbin.org
47+
└────────┴─────────┴─────────────┘
4548
```
4649

4750
#### POST
4851
```sql
49-
WITH __input AS (
50-
SELECT
51-
http_post(
52+
D WITH __input AS (
53+
SELECT
54+
http_post(
5255
'https://httpbin.org/delay/0',
5356
headers => MAP {
5457
'accept': 'application/json',
5558
},
5659
params => MAP {
5760
}
58-
) AS data
59-
),
60-
__features AS (
61-
SELECT
62-
unnest( from_json((data::JSON)->'headers', '{"Host": "VARCHAR"}') )
63-
AS features
64-
FROM
65-
__input
66-
)
61+
) AS res
62+
),
63+
__response AS (
6764
SELECT
68-
__features.Host AS host,
65+
(res->>'status')::INT AS status,
66+
(res->>'reason') AS reason,
67+
unnest( from_json(((res->>'body')::JSON)->'headers', '{"Host": "VARCHAR"}') ) AS features
6968
FROM
70-
__features
71-
;
72-
┌─────────────┐
73-
│ host │
74-
varchar
75-
├─────────────┤
76-
httpbin.org
77-
└─────────────┘
69+
__input
70+
)
71+
SELECT
72+
__response.status,
73+
__response.reason,
74+
__response.Host AS host,
75+
FROM
76+
__response
77+
;
78+
┌────────┬─────────┬─────────────┐
79+
│ status │ reason │ host │
80+
│ int32 │ varcharvarchar
81+
├────────┼─────────┼─────────────┤
82+
200 │ OK │ httpbin.org
83+
└────────┴─────────┴─────────────┘
7884
```
7985

8086
#### Full Example w/ spatial data
@@ -88,11 +94,11 @@ D WITH __input AS (
8894
SELECT
8995
http_get(
9096
'https://earth-search.aws.element84.com/v0/search')
91-
AS data
97+
AS res
9298
),
9399
__features AS (
94100
SELECT
95-
unnest( from_json((data::JSON)->'features', '["json"]') )
101+
unnest( from_json(((res->>'body')::JSON)->'features', '["json"]') )
96102
AS features
97103
FROM
98104
__input

src/http_client_extension.cpp

+49-22
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,44 @@ static std::pair<duckdb_httplib_openssl::Client, std::string> SetupHttpClient(co
4444
return std::make_pair(std::move(client), path);
4545
}
4646

47-
static void HandleHttpError(const duckdb_httplib_openssl::Result &res, const std::string &request_type) {
47+
// Helper function to escape chars of a string representing a JSON object
48+
std::string escape_json(const std::string &input) {
49+
std::ostringstream output;
50+
51+
for (auto c = input.cbegin(); c != input.cend(); c++) {
52+
switch (*c) {
53+
case '"' : output << "\\\""; break;
54+
case '\\': output << "\\\\"; break;
55+
case '\b': output << "\\b"; break;
56+
case '\f': output << "\\f"; break;
57+
case '\n': output << "\\n"; break;
58+
case '\r': output << "\\r"; break;
59+
case '\t': output << "\\t"; break;
60+
default:
61+
if ('\x00' <= *c && *c <= '\x1f') {
62+
output << "\\u"
63+
<< std::hex << std::setw(4) << std::setfill('0') << static_cast<int>(*c);
64+
} else {
65+
output << *c;
66+
}
67+
}
68+
}
69+
return output.str();
70+
}
71+
72+
// Helper function to create a Response object as a string
73+
static std::string GetJsonResponse(int status, const std::string &reason, const std::string &body) {
74+
std::string response = StringUtil::Format(
75+
"{ \"status\": %i, \"reason\": \"%s\", \"body\": \"%s\" }",
76+
status,
77+
escape_json(reason),
78+
escape_json(body)
79+
);
80+
return response;
81+
}
82+
83+
// Helper function to return the description of one HTTP error.
84+
static std::string GetHttpErrorMessage(const duckdb_httplib_openssl::Result &res, const std::string &request_type) {
4885
std::string err_message = "HTTP " + request_type + " request failed. ";
4986

5087
switch (res.error()) {
@@ -85,7 +122,7 @@ static void HandleHttpError(const duckdb_httplib_openssl::Result &res, const std
85122
err_message += "Unknown error.";
86123
break;
87124
}
88-
throw std::runtime_error(err_message);
125+
return err_message;
89126
}
90127

91128

@@ -103,17 +140,12 @@ static void HTTPGetRequestFunction(DataChunk &args, ExpressionState &state, Vect
103140
// Make the GET request
104141
auto res = client.Get(path.c_str());
105142
if (res) {
106-
if (res->status == 200) {
107-
return StringVector::AddString(result, res->body);
108-
} else {
109-
throw std::runtime_error("HTTP GET error: " + std::to_string(res->status) + " - " + res->reason);
110-
}
143+
std::string response = GetJsonResponse(res->status, res->reason, res->body);
144+
return StringVector::AddString(result, response);
111145
} else {
112-
// Handle errors
113-
HandleHttpError(res, "GET");
146+
std::string response = GetJsonResponse(-1, GetHttpErrorMessage(res, "POST"), "");
147+
return StringVector::AddString(result, response);
114148
}
115-
// Ensure a return value in case of an error
116-
return string_t();
117149
});
118150
}
119151

@@ -159,30 +191,25 @@ static void HTTPPostRequestFunction(DataChunk &args, ExpressionState &state, Vec
159191
// Make the POST request with headers and body
160192
auto res = client.Post(path.c_str(), header_map, body.val.GetString(), "application/json");
161193
if (res) {
162-
if (res->status == 200) {
163-
return StringVector::AddString(result, res->body);
164-
} else {
165-
throw std::runtime_error("HTTP POST error: " + std::to_string(res->status) + " - " + res->reason);
166-
}
194+
std::string response = GetJsonResponse(res->status, res->reason, res->body);
195+
return StringVector::AddString(result, response);
167196
} else {
168-
// Handle errors
169-
HandleHttpError(res, "POST");
197+
std::string response = GetJsonResponse(-1, GetHttpErrorMessage(res, "POST"), "");
198+
return StringVector::AddString(result, response);
170199
}
171-
// Ensure a return value in case of an error
172-
return string_t();
173200
});
174201
}
175202

176203

177204
static void LoadInternal(DatabaseInstance &instance) {
178205
ScalarFunctionSet http_get("http_get");
179-
http_get.AddFunction(ScalarFunction({LogicalType::VARCHAR}, LogicalType::VARCHAR, HTTPGetRequestFunction));
206+
http_get.AddFunction(ScalarFunction({LogicalType::VARCHAR}, LogicalType::JSON(), HTTPGetRequestFunction));
180207
ExtensionUtil::RegisterFunction(instance, http_get);
181208

182209
ScalarFunctionSet http_post("http_post");
183210
http_post.AddFunction(ScalarFunction(
184211
{LogicalType::VARCHAR, LogicalType::MAP(LogicalType::VARCHAR, LogicalType::VARCHAR), LogicalType::JSON()},
185-
LogicalType::VARCHAR, HTTPPostRequestFunction));
212+
LogicalType::JSON(), HTTPPostRequestFunction));
186213
ExtensionUtil::RegisterFunction(instance, http_post);
187214
}
188215

test/sql/httpclient.test

+60-55
Original file line numberDiff line numberDiff line change
@@ -14,55 +14,61 @@ require http_client
1414
require json
1515

1616
# Confirm the GET extension works
17-
query I
17+
query III
1818
WITH __input AS (
19-
SELECT
20-
http_get(
21-
'https://httpbin.org/delay/0'
22-
) AS data
23-
),
24-
__features AS (
25-
SELECT
26-
unnest( from_json((data::JSON)->'headers', '{"Host": "VARCHAR"}') )
27-
AS features
28-
FROM
29-
__input
30-
)
31-
SELECT
32-
__features.Host AS host,
33-
FROM
34-
__features
35-
;
19+
SELECT
20+
http_get(
21+
'https://httpbin.org/delay/0'
22+
) AS res
23+
),
24+
__response AS (
25+
SELECT
26+
(res->>'status')::INT AS status,
27+
(res->>'reason') AS reason,
28+
unnest( from_json(((res->>'body')::JSON)->'headers', '{"Host": "VARCHAR"}') ) AS features
29+
FROM
30+
__input
31+
)
32+
SELECT
33+
__response.status,
34+
__response.reason,
35+
__response.Host AS host
36+
FROM
37+
__response
38+
;
3639
----
37-
httpbin.org
40+
200 OK httpbin.org
3841

3942
# Confirm the POST extension works
40-
query I
43+
query III
4144
WITH __input AS (
42-
SELECT
43-
http_post(
44-
'https://httpbin.org/delay/0',
45-
headers => MAP {
46-
'accept': 'application/json',
47-
},
48-
params => MAP {
49-
}
50-
) AS data
51-
),
52-
__features AS (
53-
SELECT
54-
unnest( from_json((data::JSON)->'headers', '{"Host": "VARCHAR"}') )
55-
AS features
56-
FROM
57-
__input
58-
)
59-
SELECT
60-
__features.Host AS host,
61-
FROM
62-
__features
63-
;
45+
SELECT
46+
http_post(
47+
'https://httpbin.org/delay/0',
48+
headers => MAP {
49+
'accept': 'application/json',
50+
},
51+
params => MAP {
52+
}
53+
) AS res
54+
),
55+
__response AS (
56+
SELECT
57+
(res->>'status')::INT AS status,
58+
(res->>'reason') AS reason,
59+
unnest( from_json(((res->>'body')::JSON)->'headers', '{"Host": "VARCHAR"}') ) AS features
60+
FROM
61+
__input
62+
)
63+
SELECT
64+
__response.status,
65+
__response.reason,
66+
__response.Host AS host
67+
FROM
68+
__response
69+
;
6470
----
65-
httpbin.org
71+
200 OK httpbin.org
6672

6773
# Confirm the POST extension works with headers and params
6874
query I
@@ -81,19 +87,18 @@ WITH __input AS (
8187
'datetime': '2021-09-30/2021-09-30',
8288
'limit': 10
8389
}
84-
) AS data
85-
),
86-
__features AS (
87-
SELECT
88-
unnest( from_json((data::JSON)->'features', '["json"]') )
89-
AS features
90-
FROM
91-
__input
92-
)
90+
) AS res
91+
),
92+
__response AS (
9393
SELECT
94-
features->>'id' AS id
94+
unnest( from_json(((res->>'body')::JSON)->'features', '["json"]') ) AS features
9595
FROM
96-
__features
97-
;
96+
__input
97+
)
98+
SELECT
99+
features->>'id' AS id
100+
FROM
101+
__response
102+
;
98103
----
99104
S2A_56LPN_20210930_0_L2A

0 commit comments

Comments
 (0)