Skip to content

Commit 07eadf6

Browse files
committed
feat: add header to csv_options
Adds the option to do: ```sql 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) ```
1 parent 0718807 commit 07eadf6

File tree

9 files changed

+163
-15
lines changed

9 files changed

+163
-15
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ else
2626
endif
2727

2828
EXTENSION = pg_csv
29-
EXTVERSION = 0.3
29+
EXTVERSION = 0.4
3030

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

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,19 @@ select csv_agg(x, csv_options(bom := true)) from projects x;
7171
5,Orphan,
7272
(1 row)
7373
```
74+
75+
### Header
76+
77+
You can omit or include the CSV header.
78+
79+
```psql
80+
select csv_agg(x, csv_options(header := false)) from projects x;
81+
csv_agg
82+
-------------------
83+
1,Windows 7,1 +
84+
2,Windows 10,1 +
85+
3,IOS,2 +
86+
4,OSX,2 +
87+
5,Orphan,
88+
(1 row)
89+
```

sql/pg_csv--0.3--0.4.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
alter type csv_options add attribute header bool;
2+
3+
create or replace function csv_options(
4+
delimiter "char" default NULL,
5+
bom bool default NULL,
6+
header bool default NULL
7+
) returns csv_options as $$
8+
select row(delimiter, bom, header)::csv_options;
9+
$$ language sql;

sql/pg_csv.sql

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
create type csv_options as (
22
delimiter "char"
33
, bom bool
4+
, header bool
45
);
56

6-
create or replace function csv_options(delimiter "char" default NULL, bom bool default NULL) returns csv_options as $$
7-
select row(delimiter, bom)::csv_options;
7+
create or replace function csv_options(
8+
delimiter "char" default NULL,
9+
bom bool default NULL,
10+
header bool default NULL
11+
) returns csv_options as $$
12+
select row(delimiter, bom, header)::csv_options;
813
$$ language sql;
914

1015
create function csv_agg_transfn(internal, anyelement)

src/pg_csv.c

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ static const char BOM[3] = "\xEF\xBB\xBF";
1010
typedef struct {
1111
char delim;
1212
bool with_bom;
13+
bool header;
1314
} CsvOptions;
1415

1516
typedef struct {
@@ -59,14 +60,15 @@ static void parse_csv_options(HeapTupleHeader opts_hdr, CsvOptions *csv_opts) {
5960
// defaults
6061
csv_opts->delim = ',';
6162
csv_opts->with_bom = false;
63+
csv_opts->header = true;
6264

6365
if (opts_hdr == NULL) return;
6466

6567
TupleDesc desc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(opts_hdr),
6668
HeapTupleHeaderGetTypMod(opts_hdr));
6769

68-
Datum values[2];
69-
bool nulls[2];
70+
Datum values[3];
71+
bool nulls[3];
7072

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

89+
if (!nulls[2]) {
90+
csv_opts->header = DatumGetBool(values[2]);
91+
}
92+
8793
ReleaseTupleDesc(desc);
8894
}
8995

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

130136
// build header row
131-
for (int i = 0; i < tdesc->natts; i++) {
132-
Form_pg_attribute att = TupleDescAttr(tdesc, i);
133-
if (att->attisdropped) // pg always keeps dropped columns, guard against this
134-
continue;
137+
if (state->options->header) {
138+
for (int i = 0; i < tdesc->natts; i++) {
139+
Form_pg_attribute att = TupleDescAttr(tdesc, i);
140+
if (att->attisdropped) // pg always keeps dropped columns, guard against this
141+
continue;
135142

136-
if (i > 0) // only append delimiter after the first value
137-
appendStringInfoChar(&state->accum_buf, state->options->delim);
143+
if (i > 0) // only append delimiter after the first value
144+
appendStringInfoChar(&state->accum_buf, state->options->delim);
138145

139-
char *cstr = NameStr(att->attname);
140-
csv_append_field(&state->accum_buf, cstr, strlen(cstr), state->options->delim);
141-
}
146+
char *cstr = NameStr(att->attname);
147+
csv_append_field(&state->accum_buf, cstr, strlen(cstr), state->options->delim);
148+
}
142149

143-
appendStringInfoChar(&state->accum_buf, NEWLINE);
150+
appendStringInfoChar(&state->accum_buf, NEWLINE);
151+
}
144152

145153
state->tupdesc = tdesc;
146154
state->header_done = true;

test/expected/bom.out

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ FROM projects x;
3636
7;"has CR";8
3737
8;"has
3838
CRLF""";8
39+
\echo
40+
41+
\pset format aligned
42+
\pset tuples_only off

test/expected/header.out

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
-- header
2+
SELECT csv_agg(x, csv_options(header:=true)) AS body
3+
FROM projects x;
4+
body
5+
-------------------------------
6+
id,name,client_id +
7+
1,Windows 7,1 +
8+
2,"has,comma",1 +
9+
,, +
10+
4,OSX,2 +
11+
,"has""quote", +
12+
5,"has,comma and ""quote""",7+
13+
6,"has +
14+
LF",7 +
15+
7,"has \r CR",8 +
16+
8,"has \r +
17+
CRLF""",8
18+
(1 row)
19+
20+
-- no header
21+
SELECT csv_agg(x, csv_options(header:=false)) AS body
22+
FROM projects x;
23+
body
24+
-------------------------------
25+
1,Windows 7,1 +
26+
2,"has,comma",1 +
27+
,, +
28+
4,OSX,2 +
29+
,"has""quote", +
30+
5,"has,comma and ""quote""",7+
31+
6,"has +
32+
LF",7 +
33+
7,"has \r CR",8 +
34+
8,"has \r +
35+
CRLF""",8
36+
(1 row)
37+
38+
-- no header with delimiter
39+
SELECT csv_agg(x, csv_options(delimiter:='|', header:=false)) AS body
40+
FROM projects x;
41+
body
42+
-------------------------------
43+
1|Windows 7|1 +
44+
2|has,comma|1 +
45+
|| +
46+
4|OSX|2 +
47+
|"has""quote"| +
48+
5|"has,comma and ""quote"""|7+
49+
6|"has +
50+
LF"|7 +
51+
7|"has \r CR"|8 +
52+
8|"has \r +
53+
CRLF"""|8
54+
(1 row)
55+
56+
-- see bom.sql for an explanation of these settings
57+
\pset format unaligned
58+
\pset tuples_only on
59+
\echo
60+
61+
-- no header with delimiter and BOM
62+
SELECT csv_agg(x, csv_options(delimiter:='|', header:=false, bom := true)) AS body
63+
FROM projects x;
64+
1|Windows 7|1
65+
2|has,comma|1
66+
||
67+
4|OSX|2
68+
|"has""quote"|
69+
5|"has,comma and ""quote"""|7
70+
6|"has
71+
LF"|7
72+
7|"has CR"|8
73+
8|"has
74+
CRLF"""|8
75+
\echo
76+
77+
\pset format aligned
78+
\pset tuples_only off

test/sql/bom.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@ FROM projects x;
1212
-- include BOM with custom delimiter
1313
SELECT csv_agg(x, csv_options(delimiter := ';', bom := true)) AS body
1414
FROM projects x;
15+
\echo
16+
17+
\pset format aligned
18+
\pset tuples_only off

test/sql/header.sql

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
-- header
2+
SELECT csv_agg(x, csv_options(header:=true)) AS body
3+
FROM projects x;
4+
5+
-- no header
6+
SELECT csv_agg(x, csv_options(header:=false)) AS body
7+
FROM projects x;
8+
9+
-- no header with delimiter
10+
SELECT csv_agg(x, csv_options(delimiter:='|', header:=false)) AS body
11+
FROM projects x;
12+
13+
-- see bom.sql for an explanation of these settings
14+
\pset format unaligned
15+
\pset tuples_only on
16+
\echo
17+
18+
-- no header with delimiter and BOM
19+
SELECT csv_agg(x, csv_options(delimiter:='|', header:=false, bom := true)) AS body
20+
FROM projects x;
21+
\echo
22+
23+
\pset format aligned
24+
\pset tuples_only off

0 commit comments

Comments
 (0)