Skip to content

Commit 65a95fd

Browse files
authored
CSV Output (#37)
* CSV Output
1 parent b4594ef commit 65a95fd

File tree

2 files changed

+65
-23
lines changed

2 files changed

+65
-23
lines changed

.github/workflows/MainDistributionPipeline.yml

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,18 @@ concurrency:
1515
cancel-in-progress: true
1616

1717
jobs:
18-
duckdb-next-build:
19-
name: Build extension binaries
20-
uses: duckdb/extension-ci-tools/.github/workflows/_extension_distribution.yml@main
21-
with:
22-
duckdb_version: main
23-
ci_tools_version: main
24-
extension_name: httpserver
25-
2618
duckdb-stable-build:
2719
name: Build extension binaries
28-
uses: duckdb/extension-ci-tools/.github/workflows/_extension_distribution.yml@v1.2.1
20+
uses: duckdb/extension-ci-tools/.github/workflows/_extension_distribution.yml@v1.3.0
2921
with:
30-
duckdb_version: v1.2.1
31-
ci_tools_version: v1.2.1
22+
duckdb_version: v1.3.0
23+
ci_tools_version: v1.3.0
3224
extension_name: httpserver
3325

34-
duckdb-stable-deploy:
35-
name: Deploy extension binaries
36-
needs: duckdb-stable-build
37-
uses: ./.github/workflows/_extension_deploy.yml
38-
secrets: inherit
39-
with:
40-
duckdb_version: v1.2.1
41-
extension_name: httpserver
42-
deploy_latest: ${{ startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' }}
26+
# duckdb-next-build:
27+
# name: Build extension binaries
28+
# uses: duckdb/extension-ci-tools/.github/workflows/_extension_distribution.yml@main
29+
# with:
30+
# duckdb_version: main
31+
# ci_tools_version: main
32+
# extension_name: httpserver

src/httpserver_extension.cpp

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ struct HttpServerState {
3838

3939
static HttpServerState global_state;
4040

41-
// New: Base64 decoding function
41+
// Base64 decoding function
4242
std::string base64_decode(const std::string &in) {
4343
std::string out;
4444
std::vector<int> T(256, -1);
@@ -123,6 +123,54 @@ static std::string ConvertResultToNDJSON(MaterializedQueryResult &result) {
123123
return ndjson_output;
124124
}
125125

126+
static std::string ConvertResultToCSV(MaterializedQueryResult &result) {
127+
std::string csv_output;
128+
129+
// Add header row
130+
for (idx_t col = 0; col < result.ColumnCount(); ++col) {
131+
if (col > 0) {
132+
csv_output += ",";
133+
}
134+
csv_output += result.ColumnName(col);
135+
}
136+
csv_output += "\n";
137+
138+
// Add data rows
139+
for (idx_t row = 0; row < result.RowCount(); ++row) {
140+
for (idx_t col = 0; col < result.ColumnCount(); ++col) {
141+
if (col > 0) {
142+
csv_output += ",";
143+
}
144+
Value value = result.GetValue(col, row);
145+
if (value.IsNull()) {
146+
// Leave empty for NULL values
147+
continue;
148+
}
149+
150+
std::string value_str = value.ToString();
151+
// Escape quotes and wrap in quotes if contains special characters
152+
if (value_str.find_first_of(",\"\n\r") != std::string::npos) {
153+
std::string escaped;
154+
escaped.reserve(value_str.length() + 2);
155+
escaped += '"';
156+
for (char c : value_str) {
157+
if (c == '"') {
158+
escaped += '"'; // Double the quotes to escape
159+
}
160+
escaped += c;
161+
}
162+
escaped += '"';
163+
csv_output += escaped;
164+
} else {
165+
csv_output += value_str;
166+
}
167+
}
168+
csv_output += "\n";
169+
}
170+
171+
return csv_output;
172+
}
173+
126174
// Handle both GET and POST requests
127175
void HandleHttpRequest(const duckdb_httplib_openssl::Request& req, duckdb_httplib_openssl::Response& res) {
128176
std::string query;
@@ -165,7 +213,7 @@ void HandleHttpRequest(const duckdb_httplib_openssl::Request& req, duckdb_httpli
165213
return;
166214
}
167215

168-
// Set default format to JSONCompact
216+
// Set default format to JSONEachRow
169217
std::string format = "JSONEachRow";
170218

171219
// Check for format in URL parameter or header
@@ -209,6 +257,10 @@ void HandleHttpRequest(const duckdb_httplib_openssl::Request& req, duckdb_httpli
209257
ResultSerializerCompactJson serializer;
210258
std::string json_output = serializer.Serialize(*result, stats);
211259
res.set_content(json_output, "application/json");
260+
} else if (format == "CSV") {
261+
std::string csv_output = ConvertResultToCSV(*result);
262+
res.set_header("Content-Type", "text/csv");
263+
res.set_content(csv_output, "text/csv");
212264
} else {
213265
// Default to NDJSON for DuckDB's own queries
214266
std::string json_output = ConvertResultToNDJSON(*result);
@@ -217,7 +269,7 @@ void HandleHttpRequest(const duckdb_httplib_openssl::Request& req, duckdb_httpli
217269

218270
} catch (const Exception& ex) {
219271
res.status = 500;
220-
std::string error_message = "Code: 59, e.displayText() = DB::Exception: " + std::string(ex.what());
272+
std::string error_message = "DB::Exception: " + std::string(ex.what());
221273
res.set_content(error_message, "text/plain");
222274
}
223275
}

0 commit comments

Comments
 (0)