Skip to content

Commit 60ad78d

Browse files
authored
Enable map access for function, add ClickHouse dialect (apache#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.
1 parent 9569d1b commit 60ad78d

File tree

6 files changed

+105
-16
lines changed

6 files changed

+105
-16
lines changed

src/ast/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ pub enum Expr {
245245
},
246246
MapAccess {
247247
column: Box<Expr>,
248-
keys: Vec<Value>,
248+
keys: Vec<Expr>,
249249
},
250250
/// Scalar function call e.g. `LEFT(foo, 5)`
251251
Function(Function),
@@ -284,8 +284,8 @@ impl fmt::Display for Expr {
284284
write!(f, "{}", column)?;
285285
for k in keys {
286286
match k {
287-
k @ Value::Number(_, _) => write!(f, "[{}]", k)?,
288-
Value::SingleQuotedString(s) => write!(f, "[\"{}\"]", s)?,
287+
k @ Expr::Value(Value::Number(_, _)) => write!(f, "[{}]", k)?,
288+
Expr::Value(Value::SingleQuotedString(s)) => write!(f, "[\"{}\"]", s)?,
289289
_ => write!(f, "[{}]", k)?,
290290
}
291291
}

src/dialect/clickhouse.rs

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

src/dialect/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
// limitations under the License.
1212

1313
mod ansi;
14+
mod clickhouse;
1415
mod generic;
1516
mod hive;
1617
mod mssql;
@@ -23,6 +24,7 @@ use core::any::{Any, TypeId};
2324
use core::fmt::Debug;
2425

2526
pub use self::ansi::AnsiDialect;
27+
pub use self::clickhouse::ClickHouseDialect;
2628
pub use self::generic::GenericDialect;
2729
pub use self::hive::HiveDialect;
2830
pub use self::mssql::MsSqlDialect;

src/parser.rs

+10-7
Original file line numberDiff line numberDiff line change
@@ -1072,7 +1072,7 @@ impl<'a> Parser<'a> {
10721072
let key = self.parse_map_key()?;
10731073
let tok = self.consume_token(&Token::RBracket);
10741074
debug!("Tok: {}", tok);
1075-
let mut key_parts: Vec<Value> = vec![key];
1075+
let mut key_parts: Vec<Expr> = vec![key];
10761076
while self.consume_token(&Token::LBracket) {
10771077
let key = self.parse_map_key()?;
10781078
let tok = self.consume_token(&Token::RBracket);
@@ -2175,17 +2175,20 @@ impl<'a> Parser<'a> {
21752175
}
21762176

21772177
/// Parse a map key string
2178-
pub fn parse_map_key(&mut self) -> Result<Value, ParserError> {
2178+
pub fn parse_map_key(&mut self) -> Result<Expr, ParserError> {
21792179
match self.next_token() {
21802180
Token::Word(Word { value, keyword, .. }) if keyword == Keyword::NoKeyword => {
2181-
Ok(Value::SingleQuotedString(value))
2181+
if self.peek_token() == Token::LParen {
2182+
return self.parse_function(ObjectName(vec![Ident::new(value)]));
2183+
}
2184+
Ok(Expr::Value(Value::SingleQuotedString(value)))
21822185
}
2183-
Token::SingleQuotedString(s) => Ok(Value::SingleQuotedString(s)),
2186+
Token::SingleQuotedString(s) => Ok(Expr::Value(Value::SingleQuotedString(s))),
21842187
#[cfg(not(feature = "bigdecimal"))]
2185-
Token::Number(s, _) => Ok(Value::Number(s, false)),
2188+
Token::Number(s, _) => Ok(Expr::Value(Value::Number(s, false))),
21862189
#[cfg(feature = "bigdecimal")]
2187-
Token::Number(s, _) => Ok(Value::Number(s.parse().unwrap(), false)),
2188-
unexpected => self.expected("literal string or number", unexpected),
2190+
Token::Number(s, _) => Ok(Expr::Value(Value::Number(s.parse().unwrap(), false))),
2191+
unexpected => self.expected("literal string, number or function", unexpected),
21892192
}
21902193
}
21912194

tests/sqlparser_postgres.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ fn parse_map_access_expr() {
730730
value: "foo".to_string(),
731731
quote_style: None
732732
})),
733-
keys: vec![Value::Number(zero.clone(), false)]
733+
keys: vec![Expr::Value(Value::Number(zero.clone(), false))]
734734
},
735735
expr_from_projection(only(&select.projection)),
736736
);
@@ -743,8 +743,8 @@ fn parse_map_access_expr() {
743743
quote_style: None
744744
})),
745745
keys: vec![
746-
Value::Number(zero.clone(), false),
747-
Value::Number(zero.clone(), false)
746+
Expr::Value(Value::Number(zero.clone(), false)),
747+
Expr::Value(Value::Number(zero.clone(), false))
748748
]
749749
},
750750
expr_from_projection(only(&select.projection)),
@@ -758,9 +758,9 @@ fn parse_map_access_expr() {
758758
quote_style: None
759759
})),
760760
keys: vec![
761-
Value::Number(zero, false),
762-
Value::SingleQuotedString("baz".to_string()),
763-
Value::SingleQuotedString("fooz".to_string())
761+
Expr::Value(Value::Number(zero, false)),
762+
Expr::Value(Value::SingleQuotedString("baz".to_string())),
763+
Expr::Value(Value::SingleQuotedString("fooz".to_string()))
764764
]
765765
},
766766
expr_from_projection(only(&select.projection)),

tests/sqpparser_clickhouse.rs

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Licensed under the Apache License, Version 2.0 (the "License");
2+
// you may not use this file except in compliance with the License.
3+
// You may obtain a copy of the License at
4+
//
5+
// http://www.apache.org/licenses/LICENSE-2.0
6+
//
7+
// Unless required by applicable law or agreed to in writing, software
8+
// distributed under the License is distributed on an "AS IS" BASIS,
9+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
// See the License for the specific language governing permissions and
11+
// limitations under the License.
12+
13+
#![warn(clippy::all)]
14+
//! Test SQL syntax specific to ClickHouse.
15+
16+
#[macro_use]
17+
mod test_utils;
18+
use test_utils::*;
19+
20+
use sqlparser::ast::Expr::{Identifier, MapAccess};
21+
use sqlparser::ast::*;
22+
23+
use sqlparser::dialect::ClickHouseDialect;
24+
25+
#[test]
26+
fn parse_map_access_expr() {
27+
let sql = r#"SELECT string_values[indexOf(string_names, 'endpoint')] FROM foos"#;
28+
let select = clickhouse().verified_only_select(sql);
29+
assert_eq!(
30+
&MapAccess {
31+
column: Box::new(Identifier(Ident {
32+
value: "string_values".to_string(),
33+
quote_style: None
34+
})),
35+
keys: vec![Expr::Function(Function {
36+
name: ObjectName(vec!["indexOf".into()]),
37+
args: vec![
38+
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Identifier(Ident::new(
39+
"string_names"
40+
)))),
41+
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
42+
Value::SingleQuotedString("endpoint".to_string())
43+
)))
44+
],
45+
over: None,
46+
distinct: false
47+
})]
48+
},
49+
expr_from_projection(only(&select.projection)),
50+
);
51+
}
52+
53+
fn clickhouse() -> TestedDialects {
54+
TestedDialects {
55+
dialects: vec![Box::new(ClickHouseDialect {})],
56+
}
57+
}

0 commit comments

Comments
 (0)