Skip to content

Commit e400c10

Browse files
committed
Add support for named argument function
This commit support functions with argument names. the format is : "Select * from ( a => exp, b=> exp2 .. ) see: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#named-argument-assignment-token or example is snwflake db : https://docs.snowflake.com/en/sql-reference/functions/flatten.html name arugment function describe here
1 parent 8020b2e commit e400c10

File tree

4 files changed

+78
-4
lines changed

4 files changed

+78
-4
lines changed

src/ast/mod.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,10 @@ pub enum Expr {
191191
right: Box<Expr>,
192192
},
193193
/// Unary operation e.g. `NOT foo`
194-
UnaryOp { op: UnaryOperator, expr: Box<Expr> },
194+
UnaryOp {
195+
op: UnaryOperator,
196+
expr: Box<Expr>,
197+
},
195198
/// CAST an expression to a different data type e.g. `CAST(foo AS VARCHAR(123))`
196199
Cast {
197200
expr: Box<Expr>,
@@ -213,7 +216,10 @@ pub enum Expr {
213216
/// A constant of form `<data_type> 'value'`.
214217
/// This can represent ANSI SQL `DATE`, `TIME`, and `TIMESTAMP` literals (such as `DATE '2020-01-01'`),
215218
/// as well as constants of other types (a non-standard PostgreSQL extension).
216-
TypedString { data_type: DataType, value: String },
219+
TypedString {
220+
data_type: DataType,
221+
value: String,
222+
},
217223
/// Scalar function call e.g. `LEFT(foo, 5)`
218224
Function(Function),
219225
/// `CASE [<operand>] WHEN <condition> THEN <result> ... [ELSE <result>] END`
@@ -235,6 +241,11 @@ pub enum Expr {
235241
Subquery(Box<Query>),
236242
/// The `LISTAGG` function `SELECT LISTAGG(...) WITHIN GROUP (ORDER BY ...)`
237243
ListAgg(ListAgg),
244+
// Argument with name (Name=>agument)
245+
NamedArgument {
246+
name: ObjectName,
247+
value: Box<Expr>,
248+
},
238249
}
239250

240251
impl fmt::Display for Expr {
@@ -315,6 +326,7 @@ impl fmt::Display for Expr {
315326
Expr::Exists(s) => write!(f, "EXISTS ({})", s),
316327
Expr::Subquery(s) => write!(f, "({})", s),
317328
Expr::ListAgg(listagg) => write!(f, "{}", listagg),
329+
Expr::NamedArgument { name, value } => write!(f, "{} => {}", name, value),
318330
}
319331
}
320332
}

src/parser.rs

+12
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,18 @@ impl Parser {
276276
Ok(Expr::CompoundIdentifier(id_parts))
277277
}
278278
}
279+
Token::RArrow => {
280+
self.prev_token();
281+
let object_name = self.parse_object_name()?;
282+
283+
// the RArrow ('=>')
284+
let _ = self.next_token();
285+
286+
Ok(Expr::NamedArgument {
287+
name: object_name,
288+
value: Box::new(self.parse_expr()?),
289+
})
290+
}
279291
_ => Ok(Expr::Identifier(w.to_ident())),
280292
},
281293
}, // End of Token::Word

src/tokenizer.rs

+28-2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ pub enum Token {
9696
LBrace,
9797
/// Right brace `}`
9898
RBrace,
99+
/// Right Arrow `=>`
100+
RArrow,
99101
}
100102

101103
impl fmt::Display for Token {
@@ -136,6 +138,7 @@ impl fmt::Display for Token {
136138
Token::Pipe => f.write_str("|"),
137139
Token::LBrace => f.write_str("{"),
138140
Token::RBrace => f.write_str("}"),
141+
Token::RArrow => f.write_str("=>"),
139142
}
140143
}
141144
}
@@ -318,7 +321,7 @@ impl<'a> Tokenizer<'a> {
318321
}
319322
}
320323
}
321-
// identifier or keyword
324+
// identifier or Keyword
322325
ch if self.dialect.is_identifier_start(ch) => {
323326
chars.next(); // consume the first char
324327
let s = self.tokenize_word(ch, chars);
@@ -395,7 +398,13 @@ impl<'a> Tokenizer<'a> {
395398
_ => Ok(Some(Token::Pipe)),
396399
}
397400
}
398-
'=' => self.consume_and_return(chars, Token::Eq),
401+
'=' => {
402+
chars.next(); // consume
403+
match chars.peek() {
404+
Some('>') => self.consume_and_return(chars, Token::RArrow),
405+
_ => Ok(Some(Token::Eq)),
406+
}
407+
}
399408
'.' => self.consume_and_return(chars, Token::Period),
400409
'!' => {
401410
chars.next(); // consume
@@ -761,6 +770,23 @@ mod tests {
761770
compare(expected, tokens);
762771
}
763772

773+
#[test]
774+
fn tokenize_rigth_arrow() {
775+
let sql = String::from("FUCNTION(key=>value)");
776+
let dialect = GenericDialect {};
777+
let mut tokenizer = Tokenizer::new(&dialect, &sql);
778+
let tokens = tokenizer.tokenize().unwrap();
779+
let expected = vec![
780+
Token::make_word("FUCNTION", None),
781+
Token::LParen,
782+
Token::make_word("key", None),
783+
Token::RArrow,
784+
Token::make_word("value", None),
785+
Token::RParen,
786+
];
787+
compare(expected, tokens);
788+
}
789+
764790
#[test]
765791
fn tokenize_is_null() {
766792
let sql = String::from("a IS NULL");

tests/sqlparser_common.rs

+24
Original file line numberDiff line numberDiff line change
@@ -1597,6 +1597,30 @@ fn parse_scalar_function_in_projection() {
15971597
);
15981598
}
15991599

1600+
#[test]
1601+
fn parse_named_argument_function() {
1602+
let sql = "SELECT FUN(a => '1', b => '2') FROM foo";
1603+
let select = verified_only_select(sql);
1604+
assert_eq!(
1605+
&Expr::Function(Function {
1606+
name: ObjectName(vec![Ident::new("FUN")]),
1607+
args: vec![
1608+
Expr::NamedArgument {
1609+
name: ObjectName(vec![Ident::new("a")]),
1610+
value: Box::new(Expr::Value(Value::SingleQuotedString("1".to_owned())))
1611+
},
1612+
Expr::NamedArgument {
1613+
name: ObjectName(vec![Ident::new("b")]),
1614+
value: Box::new(Expr::Value(Value::SingleQuotedString("2".to_owned())))
1615+
}
1616+
],
1617+
over: None,
1618+
distinct: false,
1619+
}),
1620+
expr_from_projection(only(&select.projection))
1621+
);
1622+
}
1623+
16001624
#[test]
16011625
fn parse_window_functions() {
16021626
let sql = "SELECT row_number() OVER (ORDER BY dt DESC), \

0 commit comments

Comments
 (0)