Skip to content

Commit 00dc490

Browse files
authored
Support the string concat operator (#178)
The selected precedence is based on BigQuery documentation, where it is equal to `*` and `/`: https://cloud.google.com/bigquery/docs/reference/standard-sql/operators
1 parent 5f3c1bd commit 00dc490

File tree

4 files changed

+52
-1
lines changed

4 files changed

+52
-1
lines changed

src/ast/operator.rs

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub enum BinaryOperator {
3838
Multiply,
3939
Divide,
4040
Modulus,
41+
StringConcat,
4142
Gt,
4243
Lt,
4344
GtEq,
@@ -58,6 +59,7 @@ impl fmt::Display for BinaryOperator {
5859
BinaryOperator::Multiply => "*",
5960
BinaryOperator::Divide => "/",
6061
BinaryOperator::Modulus => "%",
62+
BinaryOperator::StringConcat => "||",
6163
BinaryOperator::Gt => ">",
6264
BinaryOperator::Lt => "<",
6365
BinaryOperator::GtEq => ">=",

src/parser.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,7 @@ impl Parser {
577577
Token::Minus => Some(BinaryOperator::Minus),
578578
Token::Mult => Some(BinaryOperator::Multiply),
579579
Token::Mod => Some(BinaryOperator::Modulus),
580+
Token::StringConcat => Some(BinaryOperator::StringConcat),
580581
Token::Div => Some(BinaryOperator::Divide),
581582
Token::Word(ref k) => match k.keyword.as_ref() {
582583
"AND" => Some(BinaryOperator::And),
@@ -708,7 +709,7 @@ impl Parser {
708709
Ok(20)
709710
}
710711
Token::Plus | Token::Minus => Ok(Self::PLUS_MINUS_PREC),
711-
Token::Mult | Token::Div | Token::Mod => Ok(40),
712+
Token::Mult | Token::Div | Token::Mod | Token::StringConcat => Ok(40),
712713
Token::DoubleColon => Ok(50),
713714
_ => Ok(0),
714715
}

src/tokenizer.rs

+33
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ pub enum Token {
6464
Div,
6565
/// Modulo Operator `%`
6666
Mod,
67+
/// String concatenation `||`
68+
StringConcat,
6769
/// Left parenthesis `(`
6870
LParen,
6971
/// Right parenthesis `)`
@@ -111,6 +113,7 @@ impl fmt::Display for Token {
111113
Token::Minus => f.write_str("-"),
112114
Token::Mult => f.write_str("*"),
113115
Token::Div => f.write_str("/"),
116+
Token::StringConcat => f.write_str("||"),
114117
Token::Mod => f.write_str("%"),
115118
Token::LParen => f.write_str("("),
116119
Token::RParen => f.write_str(")"),
@@ -374,6 +377,16 @@ impl<'a> Tokenizer<'a> {
374377
'+' => self.consume_and_return(chars, Token::Plus),
375378
'*' => self.consume_and_return(chars, Token::Mult),
376379
'%' => self.consume_and_return(chars, Token::Mod),
380+
'|' => {
381+
chars.next(); // consume the '|'
382+
match chars.peek() {
383+
Some('|') => self.consume_and_return(chars, Token::StringConcat),
384+
_ => Err(TokenizerError(format!(
385+
"Expecting to see `||`. Bitwise or operator `|` is not supported. \nError at Line: {}, Col: {}",
386+
self.line, self.col
387+
))),
388+
}
389+
}
377390
'=' => self.consume_and_return(chars, Token::Eq),
378391
'.' => self.consume_and_return(chars, Token::Period),
379392
'!' => {
@@ -562,6 +575,26 @@ mod tests {
562575
compare(expected, tokens);
563576
}
564577

578+
#[test]
579+
fn tokenize_string_string_concat() {
580+
let sql = String::from("SELECT 'a' || 'b'");
581+
let dialect = GenericDialect {};
582+
let mut tokenizer = Tokenizer::new(&dialect, &sql);
583+
let tokens = tokenizer.tokenize().unwrap();
584+
585+
let expected = vec![
586+
Token::make_keyword("SELECT"),
587+
Token::Whitespace(Whitespace::Space),
588+
Token::SingleQuotedString(String::from("a")),
589+
Token::Whitespace(Whitespace::Space),
590+
Token::StringConcat,
591+
Token::Whitespace(Whitespace::Space),
592+
Token::SingleQuotedString(String::from("b")),
593+
];
594+
595+
compare(expected, tokens);
596+
}
597+
565598
#[test]
566599
fn tokenize_simple_select() {
567600
let sql = String::from("SELECT * FROM customer WHERE id = 1 LIMIT 5");

tests/sqlparser_common.rs

+15
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,21 @@ fn parse_in_subquery() {
665665
);
666666
}
667667

668+
#[test]
669+
fn parse_string_agg() {
670+
let sql = "SELECT a || b";
671+
672+
let select = verified_only_select(sql);
673+
assert_eq!(
674+
SelectItem::UnnamedExpr(Expr::BinaryOp {
675+
left: Box::new(Expr::Identifier(Ident::new("a"))),
676+
op: BinaryOperator::StringConcat,
677+
right: Box::new(Expr::Identifier(Ident::new("b"))),
678+
}),
679+
select.projection[0]
680+
);
681+
}
682+
668683
#[test]
669684
fn parse_between() {
670685
fn chk(negated: bool) {

0 commit comments

Comments
 (0)