Skip to content

Commit 40a5ec4

Browse files
authored
CSV Output
1 parent 2b4c9be commit 40a5ec4

File tree

1 file changed

+54
-2
lines changed

1 file changed

+54
-2
lines changed

src/httpserver_extension.cpp

+54-2
Original file line numberDiff line numberDiff line change
@@ -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)