Skip to content

Commit c3eb076

Browse files
Jesse-Bakkerayman-sigma
authored andcommitted
Parse SETTINGS clause for ClickHouse table-valued functions (apache#1358)
1 parent 00b3b8b commit c3eb076

File tree

4 files changed

+167
-34
lines changed

4 files changed

+167
-34
lines changed

src/ast/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ pub use self::query::{
5050
OffsetRows, OrderBy, OrderByExpr, PivotValueSource, Query, RenameSelectItem,
5151
RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select,
5252
SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Setting, SymbolDefinition, Table,
53-
TableAlias, TableFactor, TableVersion, TableWithJoins, Top, TopQuantity, ValueTableMode,
54-
Values, WildcardAdditionalOptions, With, WithFill,
53+
TableAlias, TableFactor, TableFunctionArgs, TableVersion, TableWithJoins, Top, TopQuantity,
54+
ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill,
5555
};
5656
pub use self::value::{
5757
escape_double_quote_string, escape_quoted_string, DateTimeField, DollarQuotedString,

src/ast/query.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,19 @@ impl fmt::Display for ExprWithAlias {
899899
}
900900
}
901901

902+
/// Arguments to a table-valued function
903+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
904+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
905+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
906+
pub struct TableFunctionArgs {
907+
pub args: Vec<FunctionArg>,
908+
/// ClickHouse-specific SETTINGS clause.
909+
/// For example,
910+
/// `SELECT * FROM executable('generate_random.py', TabSeparated, 'id UInt32, random String', SETTINGS send_chunk_header = false, pool_size = 16)`
911+
/// [`executable` table function](https://clickhouse.com/docs/en/engines/table-functions/executable)
912+
pub settings: Option<Vec<Setting>>,
913+
}
914+
902915
/// A table name or a parenthesized subquery with an optional alias
903916
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
904917
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -916,7 +929,7 @@ pub enum TableFactor {
916929
/// This field's value is `Some(v)`, where `v` is a (possibly empty)
917930
/// vector of arguments, in the case of a table-valued function call,
918931
/// whereas it's `None` in the case of a regular table name.
919-
args: Option<Vec<FunctionArg>>,
932+
args: Option<TableFunctionArgs>,
920933
/// MSSQL-specific `WITH (...)` hints such as NOLOCK.
921934
with_hints: Vec<Expr>,
922935
/// Optional version qualifier to facilitate table time-travel, as
@@ -1319,7 +1332,15 @@ impl fmt::Display for TableFactor {
13191332
write!(f, "PARTITION ({})", display_comma_separated(partitions))?;
13201333
}
13211334
if let Some(args) = args {
1322-
write!(f, "({})", display_comma_separated(args))?;
1335+
write!(f, "(")?;
1336+
write!(f, "{}", display_comma_separated(&args.args))?;
1337+
if let Some(ref settings) = args.settings {
1338+
if !args.args.is_empty() {
1339+
write!(f, ", ")?;
1340+
}
1341+
write!(f, "SETTINGS {}", display_comma_separated(settings))?;
1342+
}
1343+
write!(f, ")")?;
13231344
}
13241345
if *with_ordinality {
13251346
write!(f, " WITH ORDINALITY")?;

src/parser/mod.rs

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3444,6 +3444,29 @@ impl<'a> Parser<'a> {
34443444
Ok(values)
34453445
}
34463446

3447+
/// Parse the comma of a comma-separated syntax element.
3448+
/// Returns true if there is a next element
3449+
fn is_parse_comma_separated_end(&mut self) -> bool {
3450+
if !self.consume_token(&Token::Comma) {
3451+
true
3452+
} else if self.options.trailing_commas {
3453+
let token = self.peek_token().token;
3454+
match token {
3455+
Token::Word(ref kw)
3456+
if keywords::RESERVED_FOR_COLUMN_ALIAS.contains(&kw.keyword) =>
3457+
{
3458+
true
3459+
}
3460+
Token::RParen | Token::SemiColon | Token::EOF | Token::RBracket | Token::RBrace => {
3461+
true
3462+
}
3463+
_ => false,
3464+
}
3465+
} else {
3466+
false
3467+
}
3468+
}
3469+
34473470
/// Parse a comma-separated list of 1+ items accepted by `F`
34483471
pub fn parse_comma_separated<T, F>(&mut self, mut f: F) -> Result<Vec<T>, ParserError>
34493472
where
@@ -3452,22 +3475,8 @@ impl<'a> Parser<'a> {
34523475
let mut values = vec![];
34533476
loop {
34543477
values.push(f(self)?);
3455-
if !self.consume_token(&Token::Comma) {
3478+
if self.is_parse_comma_separated_end() {
34563479
break;
3457-
} else if self.options.trailing_commas {
3458-
match self.peek_token().token {
3459-
Token::Word(kw)
3460-
if keywords::RESERVED_FOR_COLUMN_ALIAS.contains(&kw.keyword) =>
3461-
{
3462-
break;
3463-
}
3464-
Token::RParen
3465-
| Token::SemiColon
3466-
| Token::EOF
3467-
| Token::RBracket
3468-
| Token::RBrace => break,
3469-
_ => continue,
3470-
}
34713480
}
34723481
}
34733482
Ok(values)
@@ -8118,19 +8127,7 @@ impl<'a> Parser<'a> {
81188127
vec![]
81198128
};
81208129

8121-
let settings = if dialect_of!(self is ClickHouseDialect|GenericDialect)
8122-
&& self.parse_keyword(Keyword::SETTINGS)
8123-
{
8124-
let key_values = self.parse_comma_separated(|p| {
8125-
let key = p.parse_identifier(false)?;
8126-
p.expect_token(&Token::Eq)?;
8127-
let value = p.parse_value()?;
8128-
Ok(Setting { key, value })
8129-
})?;
8130-
Some(key_values)
8131-
} else {
8132-
None
8133-
};
8130+
let settings = self.parse_settings()?;
81348131

81358132
let fetch = if self.parse_keyword(Keyword::FETCH) {
81368133
Some(self.parse_fetch()?)
@@ -8177,6 +8174,23 @@ impl<'a> Parser<'a> {
81778174
}
81788175
}
81798176

8177+
fn parse_settings(&mut self) -> Result<Option<Vec<Setting>>, ParserError> {
8178+
let settings = if dialect_of!(self is ClickHouseDialect|GenericDialect)
8179+
&& self.parse_keyword(Keyword::SETTINGS)
8180+
{
8181+
let key_values = self.parse_comma_separated(|p| {
8182+
let key = p.parse_identifier(false)?;
8183+
p.expect_token(&Token::Eq)?;
8184+
let value = p.parse_value()?;
8185+
Ok(Setting { key, value })
8186+
})?;
8187+
Some(key_values)
8188+
} else {
8189+
None
8190+
};
8191+
Ok(settings)
8192+
}
8193+
81808194
/// Parse a mssql `FOR [XML | JSON | BROWSE]` clause
81818195
pub fn parse_for_clause(&mut self) -> Result<Option<ForClause>, ParserError> {
81828196
if self.parse_keyword(Keyword::XML) {
@@ -9402,9 +9416,9 @@ impl<'a> Parser<'a> {
94029416
// Parse potential version qualifier
94039417
let version = self.parse_table_version()?;
94049418

9405-
// Postgres, MSSQL: table-valued functions:
9419+
// Postgres, MSSQL, ClickHouse: table-valued functions:
94069420
let args = if self.consume_token(&Token::LParen) {
9407-
Some(self.parse_optional_args()?)
9421+
Some(self.parse_table_function_args()?)
94089422
} else {
94099423
None
94109424
};
@@ -10356,6 +10370,27 @@ impl<'a> Parser<'a> {
1035610370
}
1035710371
}
1035810372

10373+
fn parse_table_function_args(&mut self) -> Result<TableFunctionArgs, ParserError> {
10374+
if self.consume_token(&Token::RParen) {
10375+
return Ok(TableFunctionArgs {
10376+
args: vec![],
10377+
settings: None,
10378+
});
10379+
}
10380+
let mut args = vec![];
10381+
let settings = loop {
10382+
if let Some(settings) = self.parse_settings()? {
10383+
break Some(settings);
10384+
}
10385+
args.push(self.parse_function_args()?);
10386+
if self.is_parse_comma_separated_end() {
10387+
break None;
10388+
}
10389+
};
10390+
self.expect_token(&Token::RParen)?;
10391+
Ok(TableFunctionArgs { args, settings })
10392+
}
10393+
1035910394
/// Parses a potentially empty list of arguments to a window function
1036010395
/// (including the closing parenthesis).
1036110396
///

tests/sqlparser_clickhouse.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,83 @@ fn parse_create_table_on_commit_and_as_query() {
11511151
}
11521152
}
11531153

1154+
#[test]
1155+
fn parse_select_table_function_settings() {
1156+
fn check_settings(sql: &str, expected: &TableFunctionArgs) {
1157+
match clickhouse_and_generic().verified_stmt(sql) {
1158+
Statement::Query(q) => {
1159+
let from = &q.body.as_select().unwrap().from;
1160+
assert_eq!(from.len(), 1);
1161+
assert_eq!(from[0].joins, vec![]);
1162+
match &from[0].relation {
1163+
Table { args, .. } => {
1164+
let args = args.as_ref().unwrap();
1165+
assert_eq!(args, expected);
1166+
}
1167+
_ => unreachable!(),
1168+
}
1169+
}
1170+
_ => unreachable!(),
1171+
}
1172+
}
1173+
check_settings(
1174+
"SELECT * FROM table_function(arg, SETTINGS s0 = 3, s1 = 's')",
1175+
&TableFunctionArgs {
1176+
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
1177+
Expr::Identifier("arg".into()),
1178+
))],
1179+
1180+
settings: Some(vec![
1181+
Setting {
1182+
key: "s0".into(),
1183+
value: Value::Number("3".parse().unwrap(), false),
1184+
},
1185+
Setting {
1186+
key: "s1".into(),
1187+
value: Value::SingleQuotedString("s".into()),
1188+
},
1189+
]),
1190+
},
1191+
);
1192+
check_settings(
1193+
r#"SELECT * FROM table_function(arg)"#,
1194+
&TableFunctionArgs {
1195+
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
1196+
Expr::Identifier("arg".into()),
1197+
))],
1198+
settings: None,
1199+
},
1200+
);
1201+
check_settings(
1202+
"SELECT * FROM table_function(SETTINGS s0 = 3, s1 = 's')",
1203+
&TableFunctionArgs {
1204+
args: vec![],
1205+
settings: Some(vec![
1206+
Setting {
1207+
key: "s0".into(),
1208+
value: Value::Number("3".parse().unwrap(), false),
1209+
},
1210+
Setting {
1211+
key: "s1".into(),
1212+
value: Value::SingleQuotedString("s".into()),
1213+
},
1214+
]),
1215+
},
1216+
);
1217+
let invalid_cases = vec![
1218+
"SELECT * FROM t(SETTINGS a)",
1219+
"SELECT * FROM t(SETTINGS a=)",
1220+
"SELECT * FROM t(SETTINGS a=1, b)",
1221+
"SELECT * FROM t(SETTINGS a=1, b=)",
1222+
"SELECT * FROM t(SETTINGS a=1, b=c)",
1223+
];
1224+
for sql in invalid_cases {
1225+
clickhouse_and_generic()
1226+
.parse_sql_statements(sql)
1227+
.expect_err("Expected: SETTINGS key = value, found: ");
1228+
}
1229+
}
1230+
11541231
fn clickhouse() -> TestedDialects {
11551232
TestedDialects {
11561233
dialects: vec![Box::new(ClickHouseDialect {})],

0 commit comments

Comments
 (0)