@@ -101,12 +101,10 @@ pub struct CopyToStatement {
101101 pub target : String ,
102102 /// Partition keys
103103 pub partitioned_by : Vec < String > ,
104- /// Indicates whether there is a header row (e.g. CSV)
105- pub has_header : bool ,
106104 /// File type (Parquet, NDJSON, CSV etc.)
107105 pub stored_as : Option < String > ,
108106 /// Target specific options
109- pub options : Vec < ( String , Value ) > ,
107+ pub options : HashMap < String , String > ,
110108}
111109
112110impl fmt:: Display for CopyToStatement {
@@ -129,7 +127,10 @@ impl fmt::Display for CopyToStatement {
129127 }
130128
131129 if !options. is_empty ( ) {
132- let opts: Vec < _ > = options. iter ( ) . map ( |( k, v) | format ! ( "{k} {v}" ) ) . collect ( ) ;
130+ let opts: Vec < _ > = options
131+ . iter ( )
132+ . map ( |( k, v) | format ! ( "'{k}' '{v}'" ) )
133+ . collect ( ) ;
133134 write ! ( f, " OPTIONS ({})" , opts. join( ", " ) ) ?;
134135 }
135136
@@ -386,8 +387,7 @@ impl<'a> DFParser<'a> {
386387 stored_as : Option < String > ,
387388 target : Option < String > ,
388389 partitioned_by : Option < Vec < String > > ,
389- has_header : Option < bool > ,
390- options : Option < Vec < ( String , Value ) > > ,
390+ options : Option < HashMap < String , String > > ,
391391 }
392392
393393 let mut builder = Builder :: default ( ) ;
@@ -423,7 +423,7 @@ impl<'a> DFParser<'a> {
423423 }
424424 Keyword :: OPTIONS => {
425425 ensure_not_set ( & builder. options , "OPTIONS" ) ?;
426- builder. options = Some ( self . parse_value_options ( ) ?) ;
426+ builder. options = Some ( self . parse_string_options ( ) ?) ;
427427 }
428428 _ => {
429429 unreachable ! ( )
@@ -451,9 +451,8 @@ impl<'a> DFParser<'a> {
451451 source,
452452 target,
453453 partitioned_by : builder. partitioned_by . unwrap_or ( vec ! [ ] ) ,
454- has_header : builder. has_header . unwrap_or ( false ) ,
455454 stored_as : builder. stored_as ,
456- options : builder. options . unwrap_or ( vec ! [ ] ) ,
455+ options : builder. options . unwrap_or ( HashMap :: new ( ) ) ,
457456 } ) )
458457 }
459458
@@ -835,33 +834,6 @@ impl<'a> DFParser<'a> {
835834 }
836835 Ok ( options)
837836 }
838-
839- /// Parses (key value) style options into a map of String --> [`Value`].
840- ///
841- /// Unlike [`Self::parse_string_options`], this method supports
842- /// keywords as key names as well as multiple value types such as
843- /// Numbers as well as Strings.
844- fn parse_value_options ( & mut self ) -> Result < Vec < ( String , Value ) > , ParserError > {
845- let mut options = vec ! [ ] ;
846- self . parser . expect_token ( & Token :: LParen ) ?;
847-
848- loop {
849- let key = self . parse_option_key ( ) ?;
850- let value = self . parse_option_value ( ) ?;
851- options. push ( ( key, value) ) ;
852- let comma = self . parser . consume_token ( & Token :: Comma ) ;
853- if self . parser . consume_token ( & Token :: RParen ) {
854- // allow a trailing comma, even though it's not in standard
855- break ;
856- } else if !comma {
857- return self . expected (
858- "',' or ')' after option definition" ,
859- self . parser . peek_token ( ) ,
860- ) ;
861- }
862- }
863- Ok ( options)
864- }
865837}
866838
867839#[ cfg( test) ]
@@ -997,27 +969,6 @@ mod tests {
997969 } ) ;
998970 expect_parse_ok ( sql, expected) ?;
999971
1000- // positive case: it is ok for case insensitive sql stmt with has_header option tokens
1001- let sqls = vec ! [
1002- "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV LOCATION 'foo.csv' OPTIONS ('FORMAT.HAS_HEADER' 'TRUE')" ,
1003- "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV LOCATION 'foo.csv' OPTIONS ('format.has_header' 'true')" ,
1004- ] ;
1005- for sql in sqls {
1006- let expected = Statement :: CreateExternalTable ( CreateExternalTable {
1007- name : "t" . into ( ) ,
1008- columns : vec ! [ make_column_def( "c1" , DataType :: Int ( display) ) ] ,
1009- file_type : "CSV" . to_string ( ) ,
1010- location : "foo.csv" . into ( ) ,
1011- table_partition_cols : vec ! [ ] ,
1012- order_exprs : vec ! [ ] ,
1013- if_not_exists : false ,
1014- unbounded : false ,
1015- options : HashMap :: from ( [ ( "format.has_header" . into ( ) , "true" . into ( ) ) ] ) ,
1016- constraints : vec ! [ ] ,
1017- } ) ;
1018- expect_parse_ok ( sql, expected) ?;
1019- }
1020-
1021972 // positive case: it is ok for sql stmt with `COMPRESSION TYPE GZIP` tokens
1022973 let sqls = vec ! [
1023974 ( "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV LOCATION 'foo.csv' OPTIONS
@@ -1357,9 +1308,8 @@ mod tests {
13571308 source : object_name ( "foo" ) ,
13581309 target : "bar" . to_string ( ) ,
13591310 partitioned_by : vec ! [ ] ,
1360- has_header : false ,
13611311 stored_as : Some ( "CSV" . to_owned ( ) ) ,
1362- options : vec ! [ ] ,
1312+ options : HashMap :: new ( ) ,
13631313 } ) ;
13641314
13651315 assert_eq ! ( verified_stmt( sql) , expected) ;
@@ -1393,9 +1343,8 @@ mod tests {
13931343 source : object_name ( "foo" ) ,
13941344 target : "bar" . to_string ( ) ,
13951345 partitioned_by : vec ! [ ] ,
1396- has_header : false ,
13971346 stored_as : Some ( "PARQUET" . to_owned ( ) ) ,
1398- options : vec ! [ ] ,
1347+ options : HashMap :: new ( ) ,
13991348 } ) ;
14001349 let expected = Statement :: Explain ( ExplainStatement {
14011350 analyze,
@@ -1430,9 +1379,8 @@ mod tests {
14301379 source : CopyToSource :: Query ( query) ,
14311380 target : "bar" . to_string ( ) ,
14321381 partitioned_by : vec ! [ ] ,
1433- has_header : true ,
14341382 stored_as : Some ( "CSV" . to_owned ( ) ) ,
1435- options : vec ! [ ] ,
1383+ options : HashMap :: from ( [ ( "format.has_header" . into ( ) , "true" . into ( ) ) ] ) ,
14361384 } ) ;
14371385 assert_eq ! ( verified_stmt( sql) , expected) ;
14381386 Ok ( ( ) )
@@ -1445,30 +1393,22 @@ mod tests {
14451393 source : object_name ( "foo" ) ,
14461394 target : "bar" . to_string ( ) ,
14471395 partitioned_by : vec ! [ ] ,
1448- has_header : false ,
14491396 stored_as : Some ( "CSV" . to_owned ( ) ) ,
1450- options : vec ! [ (
1451- "row_group_size" . to_string( ) ,
1452- Value :: Number ( "55" . to_string( ) , false ) ,
1453- ) ] ,
1397+ options : HashMap :: from ( [ ( "row_group_size" . into ( ) , "55" . into ( ) ) ] ) ,
14541398 } ) ;
14551399 assert_eq ! ( verified_stmt( sql) , expected) ;
14561400 Ok ( ( ) )
14571401 }
14581402
14591403 #[ test]
14601404 fn copy_to_partitioned_by ( ) -> Result < ( ) , ParserError > {
1461- let sql = "COPY foo TO bar STORED AS CSV PARTITIONED BY (a) OPTIONS (row_group_size 55 )" ;
1405+ let sql = "COPY foo TO bar STORED AS CSV PARTITIONED BY (a) OPTIONS (' row_group_size' '55' )" ;
14621406 let expected = Statement :: CopyTo ( CopyToStatement {
14631407 source : object_name ( "foo" ) ,
14641408 target : "bar" . to_string ( ) ,
14651409 partitioned_by : vec ! [ "a" . to_string( ) ] ,
1466- has_header : false ,
14671410 stored_as : Some ( "CSV" . to_owned ( ) ) ,
1468- options : vec ! [ (
1469- "row_group_size" . to_string( ) ,
1470- Value :: Number ( "55" . to_string( ) , false ) ,
1471- ) ] ,
1411+ options : HashMap :: from ( [ ( "row_group_size" . to_string ( ) , "55" . into ( ) ) ] ) ,
14721412 } ) ;
14731413 assert_eq ! ( verified_stmt( sql) , expected) ;
14741414 Ok ( ( ) )
@@ -1478,18 +1418,12 @@ mod tests {
14781418 fn copy_to_multi_options ( ) -> Result < ( ) , ParserError > {
14791419 // order of options is preserved
14801420 let sql =
1481- "COPY foo TO bar STORED AS parquet OPTIONS ('format.row_group_size' 55 , 'format.compression' snappy)" ;
1421+ "COPY foo TO bar STORED AS parquet OPTIONS ('format.row_group_size' '55' , 'format.compression' ' snappy' )" ;
14821422
1483- let expected_options = vec ! [
1484- (
1485- "format.row_group_size" . to_string( ) ,
1486- Value :: Number ( "55" . to_string( ) , false ) ,
1487- ) ,
1488- (
1489- "format.compression" . to_string( ) ,
1490- Value :: UnQuotedString ( "snappy" . to_string( ) ) ,
1491- ) ,
1492- ] ;
1423+ let expected_options = HashMap :: from ( [
1424+ ( "format.row_group_size" . to_string ( ) , "55" . into ( ) ) ,
1425+ ( "format.compression" . to_string ( ) , "snappy" . into ( ) ) ,
1426+ ] ) ;
14931427
14941428 let mut statements = DFParser :: parse_sql ( sql) . unwrap ( ) ;
14951429 assert_eq ! ( statements. len( ) , 1 ) ;
@@ -1527,6 +1461,7 @@ mod tests {
15271461 /// `canonical` sql string
15281462 fn one_statement_parses_to ( sql : & str , canonical : & str ) -> Statement {
15291463 let mut statements = DFParser :: parse_sql ( sql) . unwrap ( ) ;
1464+ println ! ( "{:?}" , statements[ 0 ] ) ;
15301465 assert_eq ! ( statements. len( ) , 1 ) ;
15311466
15321467 if sql != canonical {
0 commit comments