Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
}
Expand Down
75 changes: 75 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))";
Expand Down
Loading