Enable map access for function, add ClickHouse dialect (#382)

* 1 Add ClickHouse dialects.
2 Enable map access for function.

* 1 Fixed compilation errors.
2 Modify the code according to @alamb's comments.

* Fixed compilation errors.
This commit is contained in:
Simon Liu 2021-12-22 05:16:30 +08:00 committed by GitHub
parent 9569d1b215
commit 60ad78dafc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 105 additions and 16 deletions

View file

@ -245,7 +245,7 @@ pub enum Expr {
},
MapAccess {
column: Box<Expr>,
keys: Vec<Value>,
keys: Vec<Expr>,
},
/// Scalar function call e.g. `LEFT(foo, 5)`
Function(Function),
@ -284,8 +284,8 @@ impl fmt::Display for Expr {
write!(f, "{}", column)?;
for k in keys {
match k {
k @ Value::Number(_, _) => write!(f, "[{}]", k)?,
Value::SingleQuotedString(s) => write!(f, "[\"{}\"]", s)?,
k @ Expr::Value(Value::Number(_, _)) => write!(f, "[{}]", k)?,
Expr::Value(Value::SingleQuotedString(s)) => write!(f, "[\"{}\"]", s)?,
_ => write!(f, "[{}]", k)?,
}
}

27
src/dialect/clickhouse.rs Normal file
View file

@ -0,0 +1,27 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::dialect::Dialect;
#[derive(Debug)]
pub struct ClickHouseDialect {}
impl Dialect for ClickHouseDialect {
fn is_identifier_start(&self, ch: char) -> bool {
// See https://clickhouse.com/docs/en/sql-reference/syntax/#syntax-identifiers
('a'..='z').contains(&ch) || ('A'..='Z').contains(&ch) || ch == '_'
}
fn is_identifier_part(&self, ch: char) -> bool {
self.is_identifier_start(ch) || ('0'..='9').contains(&ch)
}
}

View file

@ -11,6 +11,7 @@
// limitations under the License.
mod ansi;
mod clickhouse;
mod generic;
mod hive;
mod mssql;
@ -23,6 +24,7 @@ use core::any::{Any, TypeId};
use core::fmt::Debug;
pub use self::ansi::AnsiDialect;
pub use self::clickhouse::ClickHouseDialect;
pub use self::generic::GenericDialect;
pub use self::hive::HiveDialect;
pub use self::mssql::MsSqlDialect;

View file

@ -1072,7 +1072,7 @@ impl<'a> Parser<'a> {
let key = self.parse_map_key()?;
let tok = self.consume_token(&Token::RBracket);
debug!("Tok: {}", tok);
let mut key_parts: Vec<Value> = vec![key];
let mut key_parts: Vec<Expr> = vec![key];
while self.consume_token(&Token::LBracket) {
let key = self.parse_map_key()?;
let tok = self.consume_token(&Token::RBracket);
@ -2175,17 +2175,20 @@ impl<'a> Parser<'a> {
}
/// Parse a map key string
pub fn parse_map_key(&mut self) -> Result<Value, ParserError> {
pub fn parse_map_key(&mut self) -> Result<Expr, ParserError> {
match self.next_token() {
Token::Word(Word { value, keyword, .. }) if keyword == Keyword::NoKeyword => {
Ok(Value::SingleQuotedString(value))
if self.peek_token() == Token::LParen {
return self.parse_function(ObjectName(vec![Ident::new(value)]));
}
Ok(Expr::Value(Value::SingleQuotedString(value)))
}
Token::SingleQuotedString(s) => Ok(Value::SingleQuotedString(s)),
Token::SingleQuotedString(s) => Ok(Expr::Value(Value::SingleQuotedString(s))),
#[cfg(not(feature = "bigdecimal"))]
Token::Number(s, _) => Ok(Value::Number(s, false)),
Token::Number(s, _) => Ok(Expr::Value(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),
Token::Number(s, _) => Ok(Expr::Value(Value::Number(s.parse().unwrap(), false))),
unexpected => self.expected("literal string, number or function", unexpected),
}
}

View file

@ -730,7 +730,7 @@ fn parse_map_access_expr() {
value: "foo".to_string(),
quote_style: None
})),
keys: vec![Value::Number(zero.clone(), false)]
keys: vec![Expr::Value(Value::Number(zero.clone(), false))]
},
expr_from_projection(only(&select.projection)),
);
@ -743,8 +743,8 @@ fn parse_map_access_expr() {
quote_style: None
})),
keys: vec![
Value::Number(zero.clone(), false),
Value::Number(zero.clone(), false)
Expr::Value(Value::Number(zero.clone(), false)),
Expr::Value(Value::Number(zero.clone(), false))
]
},
expr_from_projection(only(&select.projection)),
@ -758,9 +758,9 @@ fn parse_map_access_expr() {
quote_style: None
})),
keys: vec![
Value::Number(zero, false),
Value::SingleQuotedString("baz".to_string()),
Value::SingleQuotedString("fooz".to_string())
Expr::Value(Value::Number(zero, false)),
Expr::Value(Value::SingleQuotedString("baz".to_string())),
Expr::Value(Value::SingleQuotedString("fooz".to_string()))
]
},
expr_from_projection(only(&select.projection)),

View file

@ -0,0 +1,57 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![warn(clippy::all)]
//! Test SQL syntax specific to ClickHouse.
#[macro_use]
mod test_utils;
use test_utils::*;
use sqlparser::ast::Expr::{Identifier, MapAccess};
use sqlparser::ast::*;
use sqlparser::dialect::ClickHouseDialect;
#[test]
fn parse_map_access_expr() {
let sql = r#"SELECT string_values[indexOf(string_names, 'endpoint')] FROM foos"#;
let select = clickhouse().verified_only_select(sql);
assert_eq!(
&MapAccess {
column: Box::new(Identifier(Ident {
value: "string_values".to_string(),
quote_style: None
})),
keys: vec![Expr::Function(Function {
name: ObjectName(vec!["indexOf".into()]),
args: vec![
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Identifier(Ident::new(
"string_names"
)))),
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
Value::SingleQuotedString("endpoint".to_string())
)))
],
over: None,
distinct: false
})]
},
expr_from_projection(only(&select.projection)),
);
}
fn clickhouse() -> TestedDialects {
TestedDialects {
dialects: vec![Box::new(ClickHouseDialect {})],
}
}