From 8dca4df9c3eef607039ca995378607e7831a14ad Mon Sep 17 00:00:00 2001 From: sploiselle Date: Mon, 30 Dec 2019 10:43:37 -0500 Subject: [PATCH] parse decimals without leading integer, e.g. .1 --- src/sql-parser/src/tokenizer.rs | 84 ++++++++++++++---------- src/sql-parser/tests/sqlparser_common.rs | 6 ++ test/decimal.slt | 13 ++++ 3 files changed, 70 insertions(+), 33 deletions(-) diff --git a/src/sql-parser/src/tokenizer.rs b/src/sql-parser/src/tokenizer.rs index d2dbd53b9de7a..5e21fde593ac3 100644 --- a/src/sql-parser/src/tokenizer.rs +++ b/src/sql-parser/src/tokenizer.rs @@ -394,38 +394,7 @@ impl<'a> Tokenizer<'a> { } } // numbers - '0'..='9' => { - let mut seen_decimal = false; - let mut s = peeking_take_while(chars, |ch| match ch { - '0'..='9' => true, - '.' if !seen_decimal => { - seen_decimal = true; - true - } - _ => false, - }); - // If in e-notation, parse the e-notation with special care given to negative exponents. - match chars.peek() { - Some('e') | Some('E') => { - s.push('E'); - // Consume the e-notation signifier. - chars.next(); - if let Some('-') = chars.peek() { - s.push('-'); - // Consume the negative sign. - chars.next(); - } - let e = peeking_take_while(chars, |ch| match ch { - '0'..='9' => true, - _ => false, - }); - s.push_str(&e); - } - _ => {} - } - - Ok(Some(Token::Number(s))) - } + '0'..='9' => self.tokenize_number(chars, false), // punctuation '(' => self.consume_and_return(chars, Token::LParen), ')' => self.consume_and_return(chars, Token::RParen), @@ -518,7 +487,13 @@ impl<'a> Tokenizer<'a> { } } '=' => self.consume_and_return(chars, Token::Eq), - '.' => self.consume_and_return(chars, Token::Period), + '.' => { + chars.next(); // consume '.' + match chars.peek() { + Some('0'..='9') => self.tokenize_number(chars, true), + _ => Ok(Some(Token::Period)), + } + } '!' => { chars.next(); // consume match chars.peek() { @@ -660,6 +635,49 @@ impl<'a> Tokenizer<'a> { Ok(Some(Token::Parameter(n))) } + fn tokenize_number( + &self, + chars: &mut Peekable>, + seen_decimal: bool, + ) -> Result, TokenizerError> { + let mut seen_decimal = seen_decimal; + let mut s = if seen_decimal { + ".".to_string() + } else { + String::default() + }; + + s.push_str(&peeking_take_while(chars, |ch| match ch { + '0'..='9' => true, + '.' if !seen_decimal => { + seen_decimal = true; + true + } + _ => false, + })); + // If in e-notation, parse the e-notation with special care given to negative exponents. + match chars.peek() { + Some('e') | Some('E') => { + s.push('E'); + // Consume the e-notation signifier. + chars.next(); + if let Some('-') = chars.peek() { + s.push('-'); + // Consume the negative sign. + chars.next(); + } + let e = peeking_take_while(chars, |ch| match ch { + '0'..='9' => true, + _ => false, + }); + s.push_str(&e); + } + _ => {} + } + + Ok(Some(Token::Number(s))) + } + fn consume_and_return( &self, chars: &mut Peekable>, diff --git a/src/sql-parser/tests/sqlparser_common.rs b/src/sql-parser/tests/sqlparser_common.rs index b8ce73d2ab4f4..ec283d7329430 100644 --- a/src/sql-parser/tests/sqlparser_common.rs +++ b/src/sql-parser/tests/sqlparser_common.rs @@ -485,6 +485,12 @@ fn parse_number() { assert_eq!(expr, Expr::Value(Value::Number("1.0".into()))); } +#[test] +fn parse_numeric_begin_with_decimal() { + let expr = verified_expr(".1"); + assert_eq!(expr, Expr::Value(Value::Number(".1".into()))); +} + #[test] fn parse_approximate_numeric_literal() { let expr = verified_expr("1.0E2"); diff --git a/test/decimal.slt b/test/decimal.slt index 79756642f31ee..7e8e29dbab848 100644 --- a/test/decimal.slt +++ b/test/decimal.slt @@ -294,3 +294,16 @@ query R SELECT CAST (null::float AS decimal(38, 7)) ---- NULL + +### No leading integer ### + +query R +SELECT .123 +---- +0.123 + +statement error +SELECT ..123 + +statement error +SELECT .1.23