Enable map access for numbers, multiple nesting levels (#356)

* enable integer keys for map access

* enable map access for number keys

* Add tests for string based map access

* MapAccess: unbox single quoted strings to always display double quoted strings for map access

* cargo fmt

* cargo clippy

* Fix compilation with nostd by avoiding format!

* fix codestyle

Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
This commit is contained in:
Guillaume Balaine 2021-09-24 20:22:12 +02:00 committed by GitHub
parent 014b82f03d
commit d498887a5d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 91 additions and 5 deletions

View file

@ -251,7 +251,7 @@ pub enum Expr {
}, },
MapAccess { MapAccess {
column: Box<Expr>, column: Box<Expr>,
key: String, keys: Vec<Value>,
}, },
/// Scalar function call e.g. `LEFT(foo, 5)` /// Scalar function call e.g. `LEFT(foo, 5)`
Function(Function), Function(Function),
@ -280,7 +280,17 @@ impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
Expr::Identifier(s) => write!(f, "{}", s), Expr::Identifier(s) => write!(f, "{}", s),
Expr::MapAccess { column, key } => write!(f, "{}[\"{}\"]", column, key), Expr::MapAccess { column, keys } => {
write!(f, "{}", column)?;
for k in keys {
match k {
k @ Value::Number(_, _) => write!(f, "[{}]", k)?,
Value::SingleQuotedString(s) => write!(f, "[\"{}\"]", s)?,
_ => write!(f, "[{}]", k)?,
}
}
Ok(())
}
Expr::Wildcard => f.write_str("*"), Expr::Wildcard => f.write_str("*"),
Expr::QualifiedWildcard(q) => write!(f, "{}.*", display_separated(q, ".")), Expr::QualifiedWildcard(q) => write!(f, "{}.*", display_separated(q, ".")),
Expr::CompoundIdentifier(s) => write!(f, "{}", display_separated(s, ".")), Expr::CompoundIdentifier(s) => write!(f, "{}", display_separated(s, ".")),

View file

@ -952,13 +952,20 @@ impl<'a> Parser<'a> {
} }
pub fn parse_map_access(&mut self, expr: Expr) -> Result<Expr, ParserError> { pub fn parse_map_access(&mut self, expr: Expr) -> Result<Expr, ParserError> {
let key = self.parse_literal_string()?; let key = self.parse_map_key()?;
let tok = self.consume_token(&Token::RBracket); let tok = self.consume_token(&Token::RBracket);
debug!("Tok: {}", tok); debug!("Tok: {}", tok);
let mut key_parts: Vec<Value> = vec![key];
while self.consume_token(&Token::LBracket) {
let key = self.parse_map_key()?;
let tok = self.consume_token(&Token::RBracket);
debug!("Tok: {}", tok);
key_parts.push(key);
}
match expr { match expr {
e @ Expr::Identifier(_) | e @ Expr::CompoundIdentifier(_) => Ok(Expr::MapAccess { e @ Expr::Identifier(_) | e @ Expr::CompoundIdentifier(_) => Ok(Expr::MapAccess {
column: Box::new(e), column: Box::new(e),
key, keys: key_parts,
}), }),
_ => Ok(expr), _ => Ok(expr),
} }
@ -1997,6 +2004,21 @@ impl<'a> Parser<'a> {
} }
} }
/// Parse a map key string
pub fn parse_map_key(&mut self) -> Result<Value, ParserError> {
match self.next_token() {
Token::Word(Word { value, keyword, .. }) if keyword == Keyword::NoKeyword => {
Ok(Value::SingleQuotedString(value))
}
Token::SingleQuotedString(s) => Ok(Value::SingleQuotedString(s)),
#[cfg(not(feature = "bigdecimal"))]
Token::Number(s, _) => Ok(Value::Number(s, false)),
#[cfg(feature = "bigdecimal")]
Token::Number(s, _) => Ok(Value::Number(s.parse().unwrap(), false)),
unexpected => self.expected("literal string or number", unexpected),
}
}
/// Parse a SQL datatype (in the context of a CREATE TABLE statement for example) /// Parse a SQL datatype (in the context of a CREATE TABLE statement for example)
pub fn parse_data_type(&mut self) -> Result<DataType, ParserError> { pub fn parse_data_type(&mut self) -> Result<DataType, ParserError> {
match self.next_token() { match self.next_token() {

View file

@ -194,7 +194,7 @@ fn rename_table() {
#[test] #[test]
fn map_access() { fn map_access() {
let rename = "SELECT a.b[\"asdf\"] FROM db.table WHERE a = 2"; let rename = r#"SELECT a.b["asdf"] FROM db.table WHERE a = 2"#;
hive().verified_stmt(rename); hive().verified_stmt(rename);
} }

View file

@ -18,6 +18,9 @@
mod test_utils; mod test_utils;
use test_utils::*; use test_utils::*;
#[cfg(feature = "bigdecimal")]
use bigdecimal::BigDecimal;
use sqlparser::ast::Expr::{Identifier, MapAccess};
use sqlparser::ast::*; use sqlparser::ast::*;
use sqlparser::dialect::{GenericDialect, PostgreSqlDialect}; use sqlparser::dialect::{GenericDialect, PostgreSqlDialect};
use sqlparser::parser::ParserError; use sqlparser::parser::ParserError;
@ -669,6 +672,57 @@ fn parse_pg_regex_match_ops() {
} }
} }
#[test]
fn parse_map_access_expr() {
#[cfg(not(feature = "bigdecimal"))]
let zero = "0".to_string();
#[cfg(feature = "bigdecimal")]
let zero = BigDecimal::parse_bytes(b"0", 10).unwrap();
let sql = "SELECT foo[0] FROM foos";
let select = pg_and_generic().verified_only_select(sql);
assert_eq!(
&MapAccess {
column: Box::new(Identifier(Ident {
value: "foo".to_string(),
quote_style: None
})),
keys: vec![Value::Number(zero.clone(), false)]
},
expr_from_projection(only(&select.projection)),
);
let sql = "SELECT foo[0][0] FROM foos";
let select = pg_and_generic().verified_only_select(sql);
assert_eq!(
&MapAccess {
column: Box::new(Identifier(Ident {
value: "foo".to_string(),
quote_style: None
})),
keys: vec![
Value::Number(zero.clone(), false),
Value::Number(zero.clone(), false)
]
},
expr_from_projection(only(&select.projection)),
);
let sql = r#"SELECT bar[0]["baz"]["fooz"] FROM foos"#;
let select = pg_and_generic().verified_only_select(sql);
assert_eq!(
&MapAccess {
column: Box::new(Identifier(Ident {
value: "bar".to_string(),
quote_style: None
})),
keys: vec![
Value::Number(zero, false),
Value::SingleQuotedString("baz".to_string()),
Value::SingleQuotedString("fooz".to_string())
]
},
expr_from_projection(only(&select.projection)),
);
}
fn pg() -> TestedDialects { fn pg() -> TestedDialects {
TestedDialects { TestedDialects {
dialects: vec![Box::new(PostgreSqlDialect {})], dialects: vec![Box::new(PostgreSqlDialect {})],