Skip to content
Merged
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
65 changes: 46 additions & 19 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1856,26 +1856,53 @@ impl<'a> Parser<'a> {
chain.push(AccessExpr::Dot(expr));
self.advance_token(); // The consumed string
}
// Fallback to parsing an arbitrary expression.
_ => match self.parse_subexpr(self.dialect.prec_value(Precedence::Period))? {
// If we get back a compound field access or identifier,
// we flatten the nested expression.
// For example if the current root is `foo`
// and we get back a compound identifier expression `bar.baz`
// The full expression should be `foo.bar.baz` (i.e.
// a root with an access chain with 2 entries) and not
// `foo.(bar.baz)` (i.e. a root with an access chain with
// 1 entry`).
Expr::CompoundFieldAccess { root, access_chain } => {
chain.push(AccessExpr::Dot(*root));
chain.extend(access_chain);
}
Expr::CompoundIdentifier(parts) => chain
.extend(parts.into_iter().map(Expr::Identifier).map(AccessExpr::Dot)),
expr => {
chain.push(AccessExpr::Dot(expr));
// Fallback to parsing an arbitrary expression, but restrict to expression
// types that are valid after the dot operator. This ensures that e.g.
// `T.interval` is parsed as a compound identifier, not as an interval
// expression.
_ => {
let expr = self.maybe_parse(|parser| {
let expr = parser
.parse_subexpr(parser.dialect.prec_value(Precedence::Period))?;
match &expr {
Expr::CompoundFieldAccess { .. }
| Expr::CompoundIdentifier(_)
| Expr::Identifier(_)
| Expr::Value(_)
| Expr::Function(_) => Ok(expr),
_ => parser.expected("an identifier or value", parser.peek_token()),
}
})?;

match expr {
// If we get back a compound field access or identifier,
// we flatten the nested expression.
// For example if the current root is `foo`
// and we get back a compound identifier expression `bar.baz`
// The full expression should be `foo.bar.baz` (i.e.
// a root with an access chain with 2 entries) and not
// `foo.(bar.baz)` (i.e. a root with an access chain with
// 1 entry`).
Some(Expr::CompoundFieldAccess { root, access_chain }) => {
chain.push(AccessExpr::Dot(*root));
chain.extend(access_chain);
}
Some(Expr::CompoundIdentifier(parts)) => chain.extend(
parts.into_iter().map(Expr::Identifier).map(AccessExpr::Dot),
),
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()?,
)));
}
}
},
}
}
} else if !self.dialect.supports_partiql()
&& self.peek_token_ref().token == Token::LBracket
Expand Down
45 changes: 45 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15009,6 +15009,51 @@ fn test_reserved_keywords_for_identifiers() {
dialects.parse_sql_statements(sql).unwrap();
}

#[test]
fn test_keywords_as_column_names_after_dot() {
// Test various keywords that have special meaning when standalone
// but should be treated as identifiers after a dot.
let keywords = [
"interval", // INTERVAL '1' DAY
"case", // CASE WHEN ... END
"cast", // CAST(x AS y)
"extract", // EXTRACT(DAY FROM ...)
"trim", // TRIM(...)
"substring", // SUBSTRING(...)
"left", // LEFT(str, n)
"right", // RIGHT(str, n)
];

for kw in keywords {
let sql = format!("SELECT T.{kw} FROM T");
verified_stmt(&sql);

let sql = format!("SELECT SUM(x) OVER (PARTITION BY T.{kw} ORDER BY T.id) FROM T");
verified_stmt(&sql);

let sql = format!("SELECT T.{kw}, S.{kw} FROM T, S WHERE T.{kw} = S.{kw}");
verified_stmt(&sql);
}

let select = verified_only_select("SELECT T.interval, T.case FROM T");
match &select.projection[0] {
SelectItem::UnnamedExpr(Expr::CompoundIdentifier(idents)) => {
assert_eq!(idents.len(), 2);
assert_eq!(idents[0].value, "T");
assert_eq!(idents[1].value, "interval");
}
_ => panic!("Expected CompoundIdentifier for T.interval"),
}
match &select.projection[1] {
SelectItem::UnnamedExpr(Expr::CompoundIdentifier(idents)) => {
assert_eq!(idents.len(), 2);
assert_eq!(idents[0].value, "T");
assert_eq!(idents[1].value, "case");
}
_ => panic!("Expected CompoundIdentifier for T.case"),
}
}

#[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