diff --git a/src/parser/mod.rs b/src/parser/mod.rs index db54296fce..6ea8509e92 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -2178,13 +2178,14 @@ impl<'a> Parser<'a> { Some(expr) => { chain.push(AccessExpr::Dot(expr)); } - // If the expression is not a valid suffix, fall back to - // parsing as an identifier. This handles cases like `T.interval` - // where `interval` is a keyword but should be treated as an identifier. None => { - chain.push(AccessExpr::Dot(Expr::Identifier( - self.parse_identifier()?, - ))); + let ident = self.parse_identifier()?; + let field = if self.peek_token_ref().token == Token::LParen { + self.parse_function(ObjectName::from(vec![ident]))? + } else { + Expr::Identifier(ident) + }; + chain.push(AccessExpr::Dot(field)); } } } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 4d5198953c..085853f585 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -16199,6 +16199,81 @@ fn test_keywords_as_column_names_after_dot() { } } +#[test] +fn test_qualified_keyword_function_calls() { + let pg = TestedDialects::new(vec![Box::new(PostgreSqlDialect {})]); + + for (qualifier, func) in [ + ("public", "floor"), + ("pg_catalog", "floor"), + ("pg_catalog", "ceil"), + ] { + let sql = format!("SELECT {qualifier}.{func}(3)"); + match expr_from_projection(&pg.verified_only_select(&sql).projection[0]) { + Expr::Function(Function { + name, + args: FunctionArguments::List(FunctionArgumentList { args, .. }), + .. + }) => { + assert_eq!( + name, + &ObjectName::from(vec![Ident::new(qualifier), Ident::new(func)]) + ); + assert_eq!( + args, + &[FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::value( + number("3") + )))] + ); + } + other => panic!("Expected a qualified function call, got: {other:?}"), + } + } + + for (func, sql_args) in [ + ("extract", "3"), + ("trim", "'abc'"), + ("substring", "'abc', 1, 2"), + ("overlay", "'abc', 'xy', 1"), + ("position", "'a', 'b'"), + ("convert", "'abc', 1"), + ("array", "1, 2, 3"), + ] { + let sql = format!("SELECT pg_catalog.{func}({sql_args})"); + match expr_from_projection(&pg.verified_only_select(&sql).projection[0]) { + Expr::Function(Function { name, .. }) => assert_eq!( + name, + &ObjectName::from(vec![Ident::new("pg_catalog"), Ident::new(func)]) + ), + other => panic!("Expected a qualified function call, got: {other:?}"), + } + } + + assert_matches!( + pg.parse_sql_statements("SELECT pg_catalog.overlay('abc' PLACING 'xy' FROM 1)"), + Err(ParserError::ParserError(_)) + ); + assert_matches!( + pg.parse_sql_statements("SELECT pg_catalog.position('a' IN 'b')"), + Err(ParserError::ParserError(_)) + ); + + match expr_from_projection(&pg.verified_only_select("SELECT FLOOR(3)").projection[0]) { + Expr::Floor { .. } => {} + other => panic!("Expected Expr::Floor, got: {other:?}"), + } + + match expr_from_projection( + &pg.verified_only_select("SELECT t.interval FROM tbl t") + .projection[0], + ) { + Expr::CompoundIdentifier(idents) => { + assert_eq!(idents, &vec![Ident::new("t"), Ident::new("interval")]) + } + other => panic!("Expected CompoundIdentifier, got: {other:?}"), + } +} + #[test] fn parse_create_table_with_bit_types() { let sql = "CREATE TABLE t (a BIT, b BIT VARYING, c BIT(42), d BIT VARYING(43))";