Skip to content

Commit 1e785dd

Browse files
committed
csv docs
1 parent 60ada62 commit 1e785dd

File tree

2 files changed

+43
-4
lines changed

2 files changed

+43
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- Many improvements to the [json](https://sql.datapage.app/component.sql?component=json) component, making it easier and faster than ever to build REST APIs entirely in SQL.
1313
- **Ease of use** : the component can now be used to automatically format any query result as a json array, without manually using your database''s json functions.
1414
- **server-sent events** : the component can now be used to stream query results to the client in real-time using server-sent events.
15+
- The [csv](https://sql.datapage.app/component.sql?component=csv) component can now be used as a header component to trigger a download of the CSV file directly on page load. Just select the `csv` as a component without a shell before it, and the CSV will be efficiently streamed to the user''s browser. This finally allows efficiently serving large datasets as CSV.
1516

1617
## 0.29.0 (2024-09-25)
1718
- New columns component: `columns`. Useful to display a comparison between items, or large key figures to an user.

src/render.rs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use handlebars::{BlockContext, Context, JsonValue, RenderError, Renderable};
1414
use serde::Serialize;
1515
use serde_json::{json, Value};
1616
use std::borrow::Cow;
17+
use std::io::Write;
1718
use std::sync::Arc;
1819

1920
pub enum PageContext {
@@ -227,10 +228,16 @@ impl HeaderContext {
227228
}
228229
}
229230

230-
async fn csv(mut self, _data: &JsonValue) -> anyhow::Result<PageContext> {
231+
async fn csv(mut self, options: &JsonValue) -> anyhow::Result<PageContext> {
231232
self.response
232233
.insert_header((header::CONTENT_TYPE, "text/csv"));
233-
let csv_renderer = CsvBodyRenderer::new(self.writer).await?;
234+
if let Some(filename) = get_object_str(options, "filename") {
235+
self.response.insert_header((
236+
header::CONTENT_DISPOSITION,
237+
format!("attachment; filename={filename}"),
238+
));
239+
}
240+
let csv_renderer = CsvBodyRenderer::new(self.writer, options).await?;
234241
let renderer = AnyRenderBodyContext::Csv(csv_renderer);
235242
let http_response = self.response.take();
236243
Ok(PageContext::Body {
@@ -458,11 +465,42 @@ pub struct CsvBodyRenderer {
458465
}
459466

460467
impl CsvBodyRenderer {
461-
pub async fn new(writer: ResponseWriter) -> anyhow::Result<CsvBodyRenderer> {
468+
pub async fn new(
469+
mut writer: ResponseWriter,
470+
options: &JsonValue,
471+
) -> anyhow::Result<CsvBodyRenderer> {
472+
let mut builder = csv_async::AsyncWriterBuilder::new();
473+
if let Some(delimiter) = get_object_str(options, "delimiter") {
474+
let &[delimiter_byte] = delimiter.as_bytes() else {
475+
bail!("Invalid csv delimiter: {delimiter:?}. It must be a single byte.");
476+
};
477+
builder.delimiter(delimiter_byte);
478+
}
479+
if let Some(quote) = get_object_str(options, "quote") {
480+
let &[quote_byte] = quote.as_bytes() else {
481+
bail!("Invalid csv quote: {quote:?}. It must be a single byte.");
482+
};
483+
builder.quote(quote_byte);
484+
}
485+
if let Some(escape) = get_object_str(options, "escape") {
486+
let &[escape_byte] = escape.as_bytes() else {
487+
bail!("Invalid csv escape: {escape:?}. It must be a single byte.");
488+
};
489+
builder.escape(escape_byte);
490+
}
491+
if options
492+
.get("bom")
493+
.and_then(JsonValue::as_bool)
494+
.unwrap_or(false)
495+
{
496+
let utf8_bom = b"\xEF\xBB\xBF";
497+
writer.write_all(utf8_bom)?;
498+
}
462499
let mut async_writer = AsyncResponseWriter::new(writer);
463500
tokio::io::AsyncWriteExt::flush(&mut async_writer).await?;
501+
let writer = builder.create_writer(async_writer);
464502
Ok(CsvBodyRenderer {
465-
writer: csv_async::AsyncWriter::from_writer(async_writer),
503+
writer,
466504
is_first: true,
467505
})
468506
}

0 commit comments

Comments
 (0)