Skip to content

Commit 862e887

Browse files
authored
Add CASE and IF statement support (#1741)
1 parent cf4ab7f commit 862e887

File tree

5 files changed

+473
-22
lines changed

5 files changed

+473
-22
lines changed

src/ast/mod.rs

+190-8
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,15 @@ where
151151
DisplaySeparated { slice, sep: ", " }
152152
}
153153

154+
/// Writes the given statements to the formatter, each ending with
155+
/// a semicolon and space separated.
156+
fn format_statement_list(f: &mut fmt::Formatter, statements: &[Statement]) -> fmt::Result {
157+
write!(f, "{}", display_separated(statements, "; "))?;
158+
// We manually insert semicolon for the last statement,
159+
// since display_separated doesn't handle that case.
160+
write!(f, ";")
161+
}
162+
154163
/// An identifier, decomposed into its value or character data and the quote style.
155164
#[derive(Debug, Clone, PartialOrd, Ord)]
156165
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -2080,6 +2089,173 @@ pub enum Password {
20802089
NullPassword,
20812090
}
20822091

2092+
/// A `CASE` statement.
2093+
///
2094+
/// Examples:
2095+
/// ```sql
2096+
/// CASE
2097+
/// WHEN EXISTS(SELECT 1)
2098+
/// THEN SELECT 1 FROM T;
2099+
/// WHEN EXISTS(SELECT 2)
2100+
/// THEN SELECT 1 FROM U;
2101+
/// ELSE
2102+
/// SELECT 1 FROM V;
2103+
/// END CASE;
2104+
/// ```
2105+
///
2106+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#case_search_expression)
2107+
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/snowflake-scripting/case)
2108+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2109+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2110+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
2111+
pub struct CaseStatement {
2112+
pub match_expr: Option<Expr>,
2113+
pub when_blocks: Vec<ConditionalStatements>,
2114+
pub else_block: Option<Vec<Statement>>,
2115+
/// TRUE if the statement ends with `END CASE` (vs `END`).
2116+
pub has_end_case: bool,
2117+
}
2118+
2119+
impl fmt::Display for CaseStatement {
2120+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2121+
let CaseStatement {
2122+
match_expr,
2123+
when_blocks,
2124+
else_block,
2125+
has_end_case,
2126+
} = self;
2127+
2128+
write!(f, "CASE")?;
2129+
2130+
if let Some(expr) = match_expr {
2131+
write!(f, " {expr}")?;
2132+
}
2133+
2134+
if !when_blocks.is_empty() {
2135+
write!(f, " {}", display_separated(when_blocks, " "))?;
2136+
}
2137+
2138+
if let Some(else_block) = else_block {
2139+
write!(f, " ELSE ")?;
2140+
format_statement_list(f, else_block)?;
2141+
}
2142+
2143+
write!(f, " END")?;
2144+
if *has_end_case {
2145+
write!(f, " CASE")?;
2146+
}
2147+
2148+
Ok(())
2149+
}
2150+
}
2151+
2152+
/// An `IF` statement.
2153+
///
2154+
/// Examples:
2155+
/// ```sql
2156+
/// IF TRUE THEN
2157+
/// SELECT 1;
2158+
/// SELECT 2;
2159+
/// ELSEIF TRUE THEN
2160+
/// SELECT 3;
2161+
/// ELSE
2162+
/// SELECT 4;
2163+
/// END IF
2164+
/// ```
2165+
///
2166+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#if)
2167+
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/snowflake-scripting/if)
2168+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2169+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2170+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
2171+
pub struct IfStatement {
2172+
pub if_block: ConditionalStatements,
2173+
pub elseif_blocks: Vec<ConditionalStatements>,
2174+
pub else_block: Option<Vec<Statement>>,
2175+
}
2176+
2177+
impl fmt::Display for IfStatement {
2178+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2179+
let IfStatement {
2180+
if_block,
2181+
elseif_blocks,
2182+
else_block,
2183+
} = self;
2184+
2185+
write!(f, "{if_block}")?;
2186+
2187+
if !elseif_blocks.is_empty() {
2188+
write!(f, " {}", display_separated(elseif_blocks, " "))?;
2189+
}
2190+
2191+
if let Some(else_block) = else_block {
2192+
write!(f, " ELSE ")?;
2193+
format_statement_list(f, else_block)?;
2194+
}
2195+
2196+
write!(f, " END IF")?;
2197+
2198+
Ok(())
2199+
}
2200+
}
2201+
2202+
/// Represents a type of [ConditionalStatements]
2203+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2204+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2205+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
2206+
pub enum ConditionalStatementKind {
2207+
/// `WHEN <condition> THEN <statements>`
2208+
When,
2209+
/// `IF <condition> THEN <statements>`
2210+
If,
2211+
/// `ELSEIF <condition> THEN <statements>`
2212+
ElseIf,
2213+
}
2214+
2215+
/// A block within a [Statement::Case] or [Statement::If]-like statement
2216+
///
2217+
/// Examples:
2218+
/// ```sql
2219+
/// WHEN EXISTS(SELECT 1) THEN SELECT 1;
2220+
///
2221+
/// IF TRUE THEN SELECT 1; SELECT 2;
2222+
/// ```
2223+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2224+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2225+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
2226+
pub struct ConditionalStatements {
2227+
/// The condition expression.
2228+
pub condition: Expr,
2229+
/// Statement list of the `THEN` clause.
2230+
pub statements: Vec<Statement>,
2231+
pub kind: ConditionalStatementKind,
2232+
}
2233+
2234+
impl fmt::Display for ConditionalStatements {
2235+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2236+
let ConditionalStatements {
2237+
condition: expr,
2238+
statements,
2239+
kind,
2240+
} = self;
2241+
2242+
let kind = match kind {
2243+
ConditionalStatementKind::When => "WHEN",
2244+
ConditionalStatementKind::If => "IF",
2245+
ConditionalStatementKind::ElseIf => "ELSEIF",
2246+
};
2247+
2248+
write!(f, "{kind} {expr} THEN")?;
2249+
2250+
if !statements.is_empty() {
2251+
write!(f, " ")?;
2252+
format_statement_list(f, statements)?;
2253+
}
2254+
2255+
Ok(())
2256+
}
2257+
}
2258+
20832259
/// Represents an expression assignment within a variable `DECLARE` statement.
20842260
///
20852261
/// Examples:
@@ -2647,6 +2823,10 @@ pub enum Statement {
26472823
file_format: Option<FileFormat>,
26482824
source: Box<Query>,
26492825
},
2826+
/// A `CASE` statement.
2827+
Case(CaseStatement),
2828+
/// An `IF` statement.
2829+
If(IfStatement),
26502830
/// ```sql
26512831
/// CALL <function>
26522832
/// ```
@@ -3940,6 +4120,12 @@ impl fmt::Display for Statement {
39404120
}
39414121
Ok(())
39424122
}
4123+
Statement::Case(stmt) => {
4124+
write!(f, "{stmt}")
4125+
}
4126+
Statement::If(stmt) => {
4127+
write!(f, "{stmt}")
4128+
}
39434129
Statement::AttachDatabase {
39444130
schema_name,
39454131
database_file_name,
@@ -4942,18 +5128,14 @@ impl fmt::Display for Statement {
49425128
write!(f, " {}", display_comma_separated(modes))?;
49435129
}
49445130
if !statements.is_empty() {
4945-
write!(f, " {}", display_separated(statements, "; "))?;
4946-
// We manually insert semicolon for the last statement,
4947-
// since display_separated doesn't handle that case.
4948-
write!(f, ";")?;
5131+
write!(f, " ")?;
5132+
format_statement_list(f, statements)?;
49495133
}
49505134
if let Some(exception_statements) = exception_statements {
49515135
write!(f, " EXCEPTION WHEN ERROR THEN")?;
49525136
if !exception_statements.is_empty() {
4953-
write!(f, " {}", display_separated(exception_statements, "; "))?;
4954-
// We manually insert semicolon for the last statement,
4955-
// since display_separated doesn't handle that case.
4956-
write!(f, ";")?;
5137+
write!(f, " ")?;
5138+
format_statement_list(f, exception_statements)?;
49575139
}
49585140
}
49595141
if *has_end_keyword {

src/ast/spans.rs

+64-14
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,21 @@ use crate::tokenizer::Span;
2222

2323
use super::{
2424
dcl::SecondaryRoles, value::ValueWithSpan, AccessExpr, AlterColumnOperation,
25-
AlterIndexOperation, AlterTableOperation, Array, Assignment, AssignmentTarget, CloseCursor,
26-
ClusteredIndex, ColumnDef, ColumnOption, ColumnOptionDef, ConflictTarget, ConnectBy,
27-
ConstraintCharacteristics, CopySource, CreateIndex, CreateTable, CreateTableOptions, Cte,
28-
Delete, DoUpdate, ExceptSelectItem, ExcludeSelectItem, Expr, ExprWithAlias, Fetch, FromTable,
29-
Function, FunctionArg, FunctionArgExpr, FunctionArgumentClause, FunctionArgumentList,
30-
FunctionArguments, GroupByExpr, HavingBound, IlikeSelectItem, Insert, Interpolate,
31-
InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView,
32-
LimitClause, MatchRecognizePattern, Measure, NamedWindowDefinition, ObjectName, ObjectNamePart,
33-
Offset, OnConflict, OnConflictAction, OnInsert, OrderBy, OrderByExpr, OrderByKind, Partition,
34-
PivotValueSource, ProjectionSelect, Query, ReferentialAction, RenameSelectItem,
35-
ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SqlOption,
36-
Statement, Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint,
37-
TableFactor, TableObject, TableOptionsClustered, TableWithJoins, UpdateTableFromKind, Use,
38-
Value, Values, ViewColumnDef, WildcardAdditionalOptions, With, WithFill,
25+
AlterIndexOperation, AlterTableOperation, Array, Assignment, AssignmentTarget, CaseStatement,
26+
CloseCursor, ClusteredIndex, ColumnDef, ColumnOption, ColumnOptionDef, ConditionalStatements,
27+
ConflictTarget, ConnectBy, ConstraintCharacteristics, CopySource, CreateIndex, CreateTable,
28+
CreateTableOptions, Cte, Delete, DoUpdate, ExceptSelectItem, ExcludeSelectItem, Expr,
29+
ExprWithAlias, Fetch, FromTable, Function, FunctionArg, FunctionArgExpr,
30+
FunctionArgumentClause, FunctionArgumentList, FunctionArguments, GroupByExpr, HavingBound,
31+
IfStatement, IlikeSelectItem, Insert, Interpolate, InterpolateExpr, Join, JoinConstraint,
32+
JoinOperator, JsonPath, JsonPathElem, LateralView, LimitClause, MatchRecognizePattern, Measure,
33+
NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction,
34+
OnInsert, OrderBy, OrderByExpr, OrderByKind, Partition, PivotValueSource, ProjectionSelect,
35+
Query, ReferentialAction, RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select,
36+
SelectInto, SelectItem, SetExpr, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias,
37+
TableAliasColumnDef, TableConstraint, TableFactor, TableObject, TableOptionsClustered,
38+
TableWithJoins, UpdateTableFromKind, Use, Value, Values, ViewColumnDef,
39+
WildcardAdditionalOptions, With, WithFill,
3940
};
4041

4142
/// Given an iterator of spans, return the [Span::union] of all spans.
@@ -334,6 +335,8 @@ impl Spanned for Statement {
334335
file_format: _,
335336
source,
336337
} => source.span(),
338+
Statement::Case(stmt) => stmt.span(),
339+
Statement::If(stmt) => stmt.span(),
337340
Statement::Call(function) => function.span(),
338341
Statement::Copy {
339342
source,
@@ -732,6 +735,53 @@ impl Spanned for CreateIndex {
732735
}
733736
}
734737

738+
impl Spanned for CaseStatement {
739+
fn span(&self) -> Span {
740+
let CaseStatement {
741+
match_expr,
742+
when_blocks,
743+
else_block,
744+
has_end_case: _,
745+
} = self;
746+
747+
union_spans(
748+
match_expr
749+
.iter()
750+
.map(|e| e.span())
751+
.chain(when_blocks.iter().map(|b| b.span()))
752+
.chain(else_block.iter().flat_map(|e| e.iter().map(|s| s.span()))),
753+
)
754+
}
755+
}
756+
757+
impl Spanned for IfStatement {
758+
fn span(&self) -> Span {
759+
let IfStatement {
760+
if_block,
761+
elseif_blocks,
762+
else_block,
763+
} = self;
764+
765+
union_spans(
766+
iter::once(if_block.span())
767+
.chain(elseif_blocks.iter().map(|b| b.span()))
768+
.chain(else_block.iter().flat_map(|e| e.iter().map(|s| s.span()))),
769+
)
770+
}
771+
}
772+
773+
impl Spanned for ConditionalStatements {
774+
fn span(&self) -> Span {
775+
let ConditionalStatements {
776+
condition,
777+
statements,
778+
kind: _,
779+
} = self;
780+
781+
union_spans(iter::once(condition.span()).chain(statements.iter().map(|s| s.span())))
782+
}
783+
}
784+
735785
/// # partial span
736786
///
737787
/// Missing spans:

src/keywords.rs

+1
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ define_keywords!(
297297
ELEMENT,
298298
ELEMENTS,
299299
ELSE,
300+
ELSEIF,
300301
EMPTY,
301302
ENABLE,
302303
ENABLE_SCHEMA_EVOLUTION,

0 commit comments

Comments
 (0)