@@ -22,6 +22,7 @@ use crate::common::proto_error;
2222use crate :: protobuf_common as protobuf;
2323use arrow:: array:: { ArrayRef , AsArray } ;
2424use arrow:: buffer:: Buffer ;
25+ use arrow:: csv:: writer:: Terminator ;
2526use arrow:: csv:: { QuoteStyle , WriterBuilder } ;
2627use arrow:: datatypes:: {
2728 DataType , Field , IntervalDayTimeType , IntervalMonthDayNanoType , IntervalUnit , Schema ,
@@ -985,9 +986,112 @@ impl TryFrom<&protobuf::CsvWriterOptions> for CsvWriterOptions {
985986 fn try_from (
986987 opts : & protobuf:: CsvWriterOptions ,
987988 ) -> datafusion_common:: Result < Self , Self :: Error > {
988- let write_options = csv_writer_options_from_proto ( opts) ?;
989- let compression: CompressionTypeVariant = opts. compression ( ) . into ( ) ;
990- Ok ( CsvWriterOptions :: new ( write_options, compression) )
989+ let protobuf:: CsvWriterOptions {
990+ compression,
991+ delimiter,
992+ has_header,
993+ date_format,
994+ datetime_format,
995+ timestamp_format,
996+ time_format,
997+ null_value,
998+ quote,
999+ escape,
1000+ double_quote,
1001+ quote_style,
1002+ ignore_leading_whitespace,
1003+ ignore_trailing_whitespace,
1004+ compression_level,
1005+ timestamp_tz_format,
1006+ terminator,
1007+ } = opts;
1008+
1009+ let mut writer_options = WriterBuilder :: new ( ) ;
1010+ if !delimiter. is_empty ( ) {
1011+ if let Some ( delimiter) = delimiter. chars ( ) . next ( ) {
1012+ if delimiter. is_ascii ( ) {
1013+ writer_options = writer_options. with_delimiter ( delimiter as u8 ) ;
1014+ } else {
1015+ return Err ( proto_error ( "CSV Delimiter is not ASCII" ) ) ;
1016+ }
1017+ } else {
1018+ return Err ( proto_error ( "Error parsing CSV Delimiter" ) ) ;
1019+ }
1020+ }
1021+ if !quote. is_empty ( ) {
1022+ if let Some ( quote) = quote. chars ( ) . next ( ) {
1023+ if quote. is_ascii ( ) {
1024+ writer_options = writer_options. with_quote ( quote as u8 ) ;
1025+ } else {
1026+ return Err ( proto_error ( "CSV Quote is not ASCII" ) ) ;
1027+ }
1028+ } else {
1029+ return Err ( proto_error ( "Error parsing CSV Quote" ) ) ;
1030+ }
1031+ }
1032+ if !escape. is_empty ( ) {
1033+ if let Some ( escape) = escape. chars ( ) . next ( ) {
1034+ if escape. is_ascii ( ) {
1035+ writer_options = writer_options. with_escape ( escape as u8 ) ;
1036+ } else {
1037+ return Err ( proto_error ( "CSV Escape is not ASCII" ) ) ;
1038+ }
1039+ } else {
1040+ return Err ( proto_error ( "Error parsing CSV Escape" ) ) ;
1041+ }
1042+ }
1043+ let quote_style = match protobuf:: CsvQuoteStyle :: try_from ( * quote_style) {
1044+ Ok ( protobuf:: CsvQuoteStyle :: Always ) => QuoteStyle :: Always ,
1045+ Ok ( protobuf:: CsvQuoteStyle :: NonNumeric ) => QuoteStyle :: NonNumeric ,
1046+ Ok ( protobuf:: CsvQuoteStyle :: Never ) => QuoteStyle :: Never ,
1047+ Ok ( protobuf:: CsvQuoteStyle :: Necessary ) => QuoteStyle :: Necessary ,
1048+ _ => {
1049+ return Err ( proto_error (
1050+ "Unknown quote style, must be one of: 'Always', 'NonNumeric', 'Never', 'Necessary'" ,
1051+ ) ) ;
1052+ }
1053+ } ;
1054+ writer_options = writer_options
1055+ . with_header ( * has_header)
1056+ . with_null ( null_value. clone ( ) )
1057+ . with_double_quote ( * double_quote)
1058+ . with_quote_style ( quote_style)
1059+ . with_ignore_leading_whitespace ( * ignore_leading_whitespace)
1060+ . with_ignore_trailing_whitespace ( * ignore_trailing_whitespace) ;
1061+ if !date_format. is_empty ( ) {
1062+ writer_options = writer_options. with_date_format ( date_format. clone ( ) ) ;
1063+ }
1064+ if !datetime_format. is_empty ( ) {
1065+ writer_options = writer_options. with_datetime_format ( datetime_format. clone ( ) ) ;
1066+ }
1067+ if !timestamp_format. is_empty ( ) {
1068+ writer_options =
1069+ writer_options. with_timestamp_format ( timestamp_format. clone ( ) ) ;
1070+ }
1071+ if !timestamp_tz_format. is_empty ( ) {
1072+ writer_options =
1073+ writer_options. with_timestamp_tz_format ( timestamp_tz_format. clone ( ) ) ;
1074+ }
1075+ if !time_format. is_empty ( ) {
1076+ writer_options = writer_options. with_time_format ( time_format. clone ( ) ) ;
1077+ }
1078+ writer_options = match terminator. as_slice ( ) {
1079+ [ ] => writer_options,
1080+ [ byte] => writer_options. with_line_terminator ( Terminator :: Any ( * byte) ) ,
1081+ [ b'\r' , b'\n' ] => writer_options. with_line_terminator ( Terminator :: CRLF ) ,
1082+ _ => {
1083+ return Err ( proto_error ( "CSV line terminator must be one byte or CRLF" ) ) ;
1084+ }
1085+ } ;
1086+
1087+ let compression = protobuf:: CompressionTypeVariant :: try_from ( * compression)
1088+ . unwrap_or_default ( )
1089+ . into ( ) ;
1090+ Ok ( CsvWriterOptions {
1091+ writer_options,
1092+ compression,
1093+ compression_level : * compression_level,
1094+ } )
9911095 }
9921096}
9931097
@@ -1324,66 +1428,6 @@ where
13241428 . collect :: < datafusion_common:: Result < _ , _ > > ( )
13251429}
13261430
1327- pub ( crate ) fn csv_writer_options_from_proto (
1328- writer_options : & protobuf:: CsvWriterOptions ,
1329- ) -> datafusion_common:: Result < WriterBuilder > {
1330- let mut builder = WriterBuilder :: new ( ) ;
1331- if !writer_options. delimiter . is_empty ( ) {
1332- if let Some ( delimiter) = writer_options. delimiter . chars ( ) . next ( ) {
1333- if delimiter. is_ascii ( ) {
1334- builder = builder. with_delimiter ( delimiter as u8 ) ;
1335- } else {
1336- return Err ( proto_error ( "CSV Delimiter is not ASCII" ) ) ;
1337- }
1338- } else {
1339- return Err ( proto_error ( "Error parsing CSV Delimiter" ) ) ;
1340- }
1341- }
1342- if !writer_options. quote . is_empty ( ) {
1343- if let Some ( quote) = writer_options. quote . chars ( ) . next ( ) {
1344- if quote. is_ascii ( ) {
1345- builder = builder. with_quote ( quote as u8 ) ;
1346- } else {
1347- return Err ( proto_error ( "CSV Quote is not ASCII" ) ) ;
1348- }
1349- } else {
1350- return Err ( proto_error ( "Error parsing CSV Quote" ) ) ;
1351- }
1352- }
1353- if !writer_options. escape . is_empty ( ) {
1354- if let Some ( escape) = writer_options. escape . chars ( ) . next ( ) {
1355- if escape. is_ascii ( ) {
1356- builder = builder. with_escape ( escape as u8 ) ;
1357- } else {
1358- return Err ( proto_error ( "CSV Escape is not ASCII" ) ) ;
1359- }
1360- } else {
1361- return Err ( proto_error ( "Error parsing CSV Escape" ) ) ;
1362- }
1363- }
1364- let quote_style = match protobuf:: CsvQuoteStyle :: try_from ( writer_options. quote_style )
1365- {
1366- Ok ( protobuf:: CsvQuoteStyle :: Always ) => QuoteStyle :: Always ,
1367- Ok ( protobuf:: CsvQuoteStyle :: NonNumeric ) => QuoteStyle :: NonNumeric ,
1368- Ok ( protobuf:: CsvQuoteStyle :: Never ) => QuoteStyle :: Never ,
1369- Ok ( protobuf:: CsvQuoteStyle :: Necessary ) => QuoteStyle :: Necessary ,
1370- _ => Err ( proto_error (
1371- "Unknown quote style, must be one of: 'Always', 'NonNumeric', 'Never', 'Necessary'" ,
1372- ) ) ?,
1373- } ;
1374- Ok ( builder
1375- . with_header ( writer_options. has_header )
1376- . with_date_format ( writer_options. date_format . clone ( ) )
1377- . with_datetime_format ( writer_options. datetime_format . clone ( ) )
1378- . with_timestamp_format ( writer_options. timestamp_format . clone ( ) )
1379- . with_time_format ( writer_options. time_format . clone ( ) )
1380- . with_null ( writer_options. null_value . clone ( ) )
1381- . with_double_quote ( writer_options. double_quote )
1382- . with_quote_style ( quote_style)
1383- . with_ignore_leading_whitespace ( writer_options. ignore_leading_whitespace )
1384- . with_ignore_trailing_whitespace ( writer_options. ignore_trailing_whitespace ) )
1385- }
1386-
13871431#[ cfg( test) ]
13881432mod tests {
13891433 use datafusion_common:: config:: {
0 commit comments