Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ else
endif

EXTENSION = pg_csv
EXTVERSION = 0.3
EXTVERSION = 0.4

DATA = $(wildcard sql/*--*.sql)

Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,19 @@ select csv_agg(x, csv_options(bom := true)) from projects x;
5,Orphan,
(1 row)
```

### Header

You can omit or include the CSV header.

```psql
select csv_agg(x, csv_options(header := false)) from projects x;
csv_agg
-------------------
1,Windows 7,1 +
2,Windows 10,1 +
3,IOS,2 +
4,OSX,2 +
5,Orphan,
(1 row)
```
9 changes: 9 additions & 0 deletions sql/pg_csv--0.3--0.4.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
alter type csv_options add attribute header bool;

create or replace function csv_options(
delimiter "char" default NULL,
bom bool default NULL,
header bool default NULL
) returns csv_options as $$
select row(delimiter, bom, header)::csv_options;
$$ language sql;
9 changes: 7 additions & 2 deletions sql/pg_csv.sql
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
create type csv_options as (
delimiter "char"
, bom bool
, header bool
);

create or replace function csv_options(delimiter "char" default NULL, bom bool default NULL) returns csv_options as $$
select row(delimiter, bom)::csv_options;
create or replace function csv_options(
delimiter "char" default NULL,
bom bool default NULL,
header bool default NULL
) returns csv_options as $$
select row(delimiter, bom, header)::csv_options;
$$ language sql;

create function csv_agg_transfn(internal, anyelement)
Expand Down
32 changes: 20 additions & 12 deletions src/pg_csv.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ static const char BOM[3] = "\xEF\xBB\xBF";
typedef struct {
char delim;
bool with_bom;
bool header;
} CsvOptions;

typedef struct {
Expand Down Expand Up @@ -59,14 +60,15 @@ static void parse_csv_options(HeapTupleHeader opts_hdr, CsvOptions *csv_opts) {
// defaults
csv_opts->delim = ',';
csv_opts->with_bom = false;
csv_opts->header = true;

if (opts_hdr == NULL) return;

TupleDesc desc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(opts_hdr),
HeapTupleHeaderGetTypMod(opts_hdr));

Datum values[2];
bool nulls[2];
Datum values[3];
bool nulls[3];

heap_deform_tuple(
&(HeapTupleData){.t_len = HeapTupleHeaderGetDatumLength(opts_hdr), .t_data = opts_hdr}, desc,
Expand All @@ -84,6 +86,10 @@ static void parse_csv_options(HeapTupleHeader opts_hdr, CsvOptions *csv_opts) {
csv_opts->with_bom = DatumGetBool(values[1]);
}

if (!nulls[2]) {
csv_opts->header = DatumGetBool(values[2]);
}

ReleaseTupleDesc(desc);
}

Expand Down Expand Up @@ -128,19 +134,21 @@ Datum csv_agg_transfn(PG_FUNCTION_ARGS) {
if (state->options->with_bom) appendBinaryStringInfo(&state->accum_buf, BOM, sizeof(BOM));

// build header row
for (int i = 0; i < tdesc->natts; i++) {
Form_pg_attribute att = TupleDescAttr(tdesc, i);
if (att->attisdropped) // pg always keeps dropped columns, guard against this
continue;
if (state->options->header) {
for (int i = 0; i < tdesc->natts; i++) {
Form_pg_attribute att = TupleDescAttr(tdesc, i);
if (att->attisdropped) // pg always keeps dropped columns, guard against this
continue;

if (i > 0) // only append delimiter after the first value
appendStringInfoChar(&state->accum_buf, state->options->delim);
if (i > 0) // only append delimiter after the first value
appendStringInfoChar(&state->accum_buf, state->options->delim);

char *cstr = NameStr(att->attname);
csv_append_field(&state->accum_buf, cstr, strlen(cstr), state->options->delim);
}
char *cstr = NameStr(att->attname);
csv_append_field(&state->accum_buf, cstr, strlen(cstr), state->options->delim);
}

appendStringInfoChar(&state->accum_buf, NEWLINE);
appendStringInfoChar(&state->accum_buf, NEWLINE);
}

state->tupdesc = tdesc;
state->header_done = true;
Expand Down
4 changes: 4 additions & 0 deletions test/expected/bom.out
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ FROM projects x;
7;"has CR";8
8;"has
CRLF""";8
\echo

\pset format aligned
\pset tuples_only off
Expand Down
78 changes: 78 additions & 0 deletions test/expected/header.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
-- header
SELECT csv_agg(x, csv_options(header:=true)) AS body
FROM projects x;
body
-------------------------------
id,name,client_id +
1,Windows 7,1 +
2,"has,comma",1 +
,, +
4,OSX,2 +
,"has""quote", +
5,"has,comma and ""quote""",7+
6,"has +
LF",7 +
7,"has \r CR",8 +
8,"has \r +
CRLF""",8
(1 row)

-- no header
SELECT csv_agg(x, csv_options(header:=false)) AS body
FROM projects x;
body
-------------------------------
1,Windows 7,1 +
2,"has,comma",1 +
,, +
4,OSX,2 +
,"has""quote", +
5,"has,comma and ""quote""",7+
6,"has +
LF",7 +
7,"has \r CR",8 +
8,"has \r +
CRLF""",8
(1 row)

-- no header with delimiter
SELECT csv_agg(x, csv_options(delimiter:='|', header:=false)) AS body
FROM projects x;
body
-------------------------------
1|Windows 7|1 +
2|has,comma|1 +
|| +
4|OSX|2 +
|"has""quote"| +
5|"has,comma and ""quote"""|7+
6|"has +
LF"|7 +
7|"has \r CR"|8 +
8|"has \r +
CRLF"""|8
(1 row)

-- see bom.sql for an explanation of these settings
\pset format unaligned
\pset tuples_only on
\echo

-- no header with delimiter and BOM
SELECT csv_agg(x, csv_options(delimiter:='|', header:=false, bom := true)) AS body
FROM projects x;
1|Windows 7|1
2|has,comma|1
||
4|OSX|2
|"has""quote"|
5|"has,comma and ""quote"""|7
6|"has
LF"|7
7|"has CR"|8
8|"has
CRLF"""|8
\echo

\pset format aligned
\pset tuples_only off
Expand Down
4 changes: 4 additions & 0 deletions test/sql/bom.sql
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ FROM projects x;
-- include BOM with custom delimiter
SELECT csv_agg(x, csv_options(delimiter := ';', bom := true)) AS body
FROM projects x;
\echo

\pset format aligned
\pset tuples_only off
24 changes: 24 additions & 0 deletions test/sql/header.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-- header
SELECT csv_agg(x, csv_options(header:=true)) AS body
FROM projects x;

-- no header
SELECT csv_agg(x, csv_options(header:=false)) AS body
FROM projects x;

-- no header with delimiter
SELECT csv_agg(x, csv_options(delimiter:='|', header:=false)) AS body
FROM projects x;

-- see bom.sql for an explanation of these settings
\pset format unaligned
\pset tuples_only on
\echo

-- no header with delimiter and BOM
SELECT csv_agg(x, csv_options(delimiter:='|', header:=false, bom := true)) AS body
FROM projects x;
\echo

\pset format aligned
\pset tuples_only off
Loading