33PG_MODULE_MAGIC ;
44
55static const char NEWLINE = '\n' ;
6- static const char DELIMITER = ',' ;
76static const char DQUOTE = '"' ;
87static const char CR = '\r' ;
98
@@ -14,17 +13,21 @@ typedef struct {
1413 TupleDesc tupdesc ;
1514} CsvAggState ;
1615
16+ static inline bool is_reserved (char c ){
17+ return c == DQUOTE || c == NEWLINE || c == CR ;
18+ }
19+
1720// Any comma, quote, CR, LF requires quoting as per RFC https://www.ietf.org/rfc/rfc4180.txt
18- static inline bool needs_quote (const char * s , size_t n ) {
21+ static inline bool needs_quote (const char * s , size_t n , char delim ) {
1922 while (n -- ) {
2023 char c = * s ++ ;
21- if (c == DELIMITER || c == DQUOTE || c == NEWLINE || c == CR ) return true;
24+ if (c == delim || is_reserved ( c ) ) return true;
2225 }
2326 return false;
2427}
2528
26- static inline void csv_append_field (StringInfo buf , const char * s , size_t n ) {
27- if (!needs_quote (s , n )) {
29+ static inline void csv_append_field (StringInfo buf , const char * s , size_t n , char delim ) {
30+ if (!needs_quote (s , n , delim )) {
2831 appendBinaryStringInfo (buf , s , n );
2932 } else {
3033 appendStringInfoChar (buf , DQUOTE );
@@ -72,6 +75,15 @@ Datum csv_agg_transfn(PG_FUNCTION_ARGS) {
7275
7376 HeapTupleHeader next = PG_GETARG_HEAPTUPLEHEADER (1 );
7477
78+ char delim = PG_NARGS () >= 3 && !PG_ARGISNULL (2 )?
79+ PG_GETARG_CHAR (2 ) :
80+ ',' ;
81+
82+ if (is_reserved (delim ))
83+ ereport (ERROR ,
84+ (errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
85+ errmsg ("delimiter cannot be newline, carriage return or double quote" )));
86+
7587 // build header and cache tupdesc once
7688 if (!state -> header_done ) {
7789 TupleDesc tdesc = lookup_rowtype_tupdesc (HeapTupleHeaderGetTypeId (next ), HeapTupleHeaderGetTypMod (next ));
@@ -83,10 +95,10 @@ Datum csv_agg_transfn(PG_FUNCTION_ARGS) {
8395 continue ;
8496
8597 if (i > 0 ) // only append delimiter after the first value
86- appendStringInfoChar (& state -> accum_buf , DELIMITER );
98+ appendStringInfoChar (& state -> accum_buf , delim );
8799
88100 char * cstr = NameStr (att -> attname );
89- csv_append_field (& state -> accum_buf , cstr , strlen (cstr ));
101+ csv_append_field (& state -> accum_buf , cstr , strlen (cstr ), delim );
90102 }
91103
92104 appendStringInfoChar (& state -> accum_buf , NEWLINE );
@@ -119,12 +131,12 @@ Datum csv_agg_transfn(PG_FUNCTION_ARGS) {
119131 if (att -> attisdropped ) // pg always keeps dropped columns, guard against this
120132 continue ;
121133
122- if (i > 0 ) appendStringInfoChar (& state -> accum_buf , DELIMITER );
134+ if (i > 0 ) appendStringInfoChar (& state -> accum_buf , delim );
123135
124136 if (nulls [i ]) continue ; // empty field for NULL
125137
126138 char * cstr = datum_to_cstring (datums [i ], att -> atttypid );
127- csv_append_field (& state -> accum_buf , cstr , strlen (cstr ));
139+ csv_append_field (& state -> accum_buf , cstr , strlen (cstr ), delim );
128140 }
129141
130142 PG_RETURN_POINTER (state );
0 commit comments