Skip to content

Commit d49acc6

Browse files
authored
Parse SETTINGS clause for ClickHouse table-valued functions (#1358)
1 parent a692ba5 commit d49acc6

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
@@ -1314,7 +1327,15 @@ impl fmt::Display for TableFactor {
13141327
write!(f, "PARTITION ({})", display_comma_separated(partitions))?;
13151328
}
13161329
if let Some(args) = args {
1317-
write!(f, "({})", display_comma_separated(args))?;
1330+
write!(f, "(")?;
1331+
write!(f, "{}", display_comma_separated(&args.args))?;
1332+
if let Some(ref settings) = args.settings {
1333+
if !args.args.is_empty() {
1334+
write!(f, ", ")?;
1335+
}
1336+
write!(f, "SETTINGS {}", display_comma_separated(settings))?;
1337+
}
1338+
write!(f, ")")?;
13181339
}
13191340
if *with_ordinality {
13201341
write!(f, " WITH ORDINALITY")?;

src/parser/mod.rs

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3430,6 +3430,29 @@ impl<'a> Parser<'a> {
34303430
Ok(values)
34313431
}
34323432

3433+
/// Parse the comma of a comma-separated syntax element.
3434+
/// Returns true if there is a next element
3435+
fn is_parse_comma_separated_end(&mut self) -> bool {
3436+
if !self.consume_token(&Token::Comma) {
3437+
true
3438+
} else if self.options.trailing_commas {
3439+
let token = self.peek_token().token;
3440+
match token {
3441+
Token::Word(ref kw)
3442+
if keywords::RESERVED_FOR_COLUMN_ALIAS.contains(&kw.keyword) =>
3443+
{
3444+
true
3445+
}
3446+
Token::RParen | Token::SemiColon | Token::EOF | Token::RBracket | Token::RBrace => {
3447+
true
3448+
}
3449+
_ => false,
3450+
}
3451+
} else {
3452+
false
3453+
}
3454+
}
3455+
34333456
/// Parse a comma-separated list of 1+ items accepted by `F`
34343457
pub fn parse_comma_separated<T, F>(&mut self, mut f: F) -> Result<Vec<T>, ParserError>
34353458
where
@@ -3438,22 +3461,8 @@ impl<'a> Parser<'a> {
34383461
let mut values = vec![];
34393462
loop {
34403463
values.push(f(self)?);
3441-
if !self.consume_token(&Token::Comma) {
3464+
if self.is_parse_comma_separated_end() {
34423465
break;
3443-
} else if self.options.trailing_commas {
3444-
match self.peek_token().token {
3445-
Token::Word(kw)
3446-
if keywords::RESERVED_FOR_COLUMN_ALIAS.contains(&kw.keyword) =>
3447-
{
3448-
break;
3449-
}
3450-
Token::RParen
3451-
| Token::SemiColon
3452-
| Token::EOF
3453-
| Token::RBracket
3454-
| Token::RBrace => break,
3455-
_ => continue,
3456-
}
34573466
}
34583467
}
34593468
Ok(values)
@@ -8104,19 +8113,7 @@ impl<'a> Parser<'a> {
81048113
vec![]
81058114
};
81068115

8107-
let settings = if dialect_of!(self is ClickHouseDialect|GenericDialect)
8108-
&& self.parse_keyword(Keyword::SETTINGS)
8109-
{
8110-
let key_values = self.parse_comma_separated(|p| {
8111-
let key = p.parse_identifier(false)?;
8112-
p.expect_token(&Token::Eq)?;
8113-
let value = p.parse_value()?;
8114-
Ok(Setting { key, value })
8115-
})?;
8116-
Some(key_values)
8117-
} else {
8118-
None
8119-
};
8116+
let settings = self.parse_settings()?;
81208117

81218118
let fetch = if self.parse_keyword(Keyword::FETCH) {
81228119
Some(self.parse_fetch()?)
@@ -8163,6 +8160,23 @@ impl<'a> Parser<'a> {
81638160
}
81648161
}
81658162

8163+
fn parse_settings(&mut self) -> Result<Option<Vec<Setting>>, ParserError> {
8164+
let settings = if dialect_of!(self is ClickHouseDialect|GenericDialect)
8165+
&& self.parse_keyword(Keyword::SETTINGS)
8166+
{
8167+
let key_values = self.parse_comma_separated(|p| {
8168+
let key = p.parse_identifier(false)?;
8169+
p.expect_token(&Token::Eq)?;
8170+
let value = p.parse_value()?;
8171+
Ok(Setting { key, value })
8172+
})?;
8173+
Some(key_values)
8174+
} else {
8175+
None
8176+
};
8177+
Ok(settings)
8178+
}
8179+
81668180
/// Parse a mssql `FOR [XML | JSON | BROWSE]` clause
81678181
pub fn parse_for_clause(&mut self) -> Result<Option<ForClause>, ParserError> {
81688182
if self.parse_keyword(Keyword::XML) {
@@ -9382,9 +9396,9 @@ impl<'a> Parser<'a> {
93829396
// Parse potential version qualifier
93839397
let version = self.parse_table_version()?;
93849398

9385-
// Postgres, MSSQL: table-valued functions:
9399+
// Postgres, MSSQL, ClickHouse: table-valued functions:
93869400
let args = if self.consume_token(&Token::LParen) {
9387-
Some(self.parse_optional_args()?)
9401+
Some(self.parse_table_function_args()?)
93889402
} else {
93899403
None
93909404
};
@@ -10327,6 +10341,27 @@ impl<'a> Parser<'a> {
1032710341
}
1032810342
}
1032910343

10344+
fn parse_table_function_args(&mut self) -> Result<TableFunctionArgs, ParserError> {
10345+
if self.consume_token(&Token::RParen) {
10346+
return Ok(TableFunctionArgs {
10347+
args: vec![],
10348+
settings: None,
10349+
});
10350+
}
10351+
let mut args = vec![];
10352+
let settings = loop {
10353+
if let Some(settings) = self.parse_settings()? {
10354+
break Some(settings);
10355+
}
10356+
args.push(self.parse_function_args()?);
10357+
if self.is_parse_comma_separated_end() {
10358+
break None;
10359+
}
10360+
};
10361+
self.expect_token(&Token::RParen)?;
10362+
Ok(TableFunctionArgs { args, settings })
10363+
}
10364+
1033010365
/// Parses a potentially empty list of arguments to a window function
1033110366
/// (including the closing parenthesis).
1033210367
///

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)