mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-26 15:39:12 +00:00
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:
parent
014b82f03d
commit
d498887a5d
4 changed files with 91 additions and 5 deletions
|
@ -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, ".")),
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {})],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue