diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 19c11d29..ece9ba65 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -3034,7 +3034,6 @@ impl<'a> Parser<'a> { where F: FnMut(&mut Parser<'a>) -> Result<(StructField, MatchedTrailingBracket), ParserError>, { - let start_token = self.peek_token(); self.expect_keyword_is(Keyword::STRUCT)?; // Nothing to do if we have no type information. @@ -3047,16 +3046,10 @@ impl<'a> Parser<'a> { let trailing_bracket = loop { let (def, trailing_bracket) = elem_parser(self)?; field_defs.push(def); - if !self.consume_token(&Token::Comma) { + // The struct field definition is finished if it occurs `>>` or comma. + if trailing_bracket.0 || !self.consume_token(&Token::Comma) { break trailing_bracket; } - - // Angle brackets are balanced so we only expect the trailing `>>` after - // we've matched all field types for the current struct. - // e.g. this is invalid syntax `STRUCT>>, INT>(NULL)` - if trailing_bracket.0 { - return parser_err!("unmatched > in STRUCT definition", start_token.span.start); - } }; Ok(( diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index e37e4a44..810d8480 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -635,35 +635,6 @@ fn parse_nested_data_types() { } } -#[test] -fn parse_invalid_brackets() { - let sql = "SELECT STRUCT>(NULL)"; - assert_eq!( - bigquery_and_generic() - .parse_sql_statements(sql) - .unwrap_err(), - ParserError::ParserError("unmatched > in STRUCT literal".to_string()) - ); - - let sql = "SELECT STRUCT>>(NULL)"; - assert_eq!( - bigquery_and_generic() - .parse_sql_statements(sql) - .unwrap_err(), - ParserError::ParserError("Expected: (, found: >".to_string()) - ); - - let sql = "CREATE TABLE table (x STRUCT>>)"; - assert_eq!( - bigquery_and_generic() - .parse_sql_statements(sql) - .unwrap_err(), - ParserError::ParserError( - "Expected: ',' or ')' after column definition, found: >".to_string() - ) - ); -} - #[test] fn parse_tuple_struct_literal() { // tuple syntax: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#tuple_syntax @@ -2472,3 +2443,78 @@ fn test_struct_field_options() { ")", )); } + +#[test] +fn test_struct_trailing_and_nested_bracket() { + bigquery().verified_stmt(concat!( + "CREATE TABLE my_table (", + "f0 STRING, ", + "f1 STRUCT>, ", + "f2 STRING", + ")", + )); + + // More complex nested structs + bigquery().verified_stmt(concat!( + "CREATE TABLE my_table (", + "f0 STRING, ", + "f1 STRUCT>>, ", + "f2 STRUCT>>>, ", + "f3 STRUCT>", + ")", + )); + + // Bad case with missing closing bracket + assert_eq!( + ParserError::ParserError("Expected: >, found: )".to_owned()), + bigquery() + .parse_sql_statements("CREATE TABLE my_table(f1 STRUCT after parsing data type STRUCT)".to_owned() + ), + bigquery() + .parse_sql_statements("CREATE TABLE my_table(f1 STRUCT>)") + .unwrap_err() + ); + + // Base case with redundant closing bracket in nested struct + assert_eq!( + ParserError::ParserError( + "Expected: ',' or ')' after column definition, found: >".to_owned() + ), + bigquery() + .parse_sql_statements("CREATE TABLE my_table(f1 STRUCT>>, c INT64)") + .unwrap_err() + ); + + let sql = "SELECT STRUCT>(NULL)"; + assert_eq!( + bigquery_and_generic() + .parse_sql_statements(sql) + .unwrap_err(), + ParserError::ParserError("unmatched > in STRUCT literal".to_string()) + ); + + let sql = "SELECT STRUCT>>(NULL)"; + assert_eq!( + bigquery_and_generic() + .parse_sql_statements(sql) + .unwrap_err(), + ParserError::ParserError("Expected: (, found: >".to_string()) + ); + + let sql = "CREATE TABLE table (x STRUCT>>)"; + assert_eq!( + bigquery_and_generic() + .parse_sql_statements(sql) + .unwrap_err(), + ParserError::ParserError( + "Expected: ',' or ')' after column definition, found: >".to_string() + ) + ); +}