Skip to content

Commit 76bfa87

Browse files
HiranmayaGunduJichaoS
authored andcommitted
feat: implement select * ilike for snowflake (apache#1228)
1 parent d1ee270 commit 76bfa87

File tree

6 files changed

+100
-3
lines changed

6 files changed

+100
-3
lines changed

src/ast/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ pub use self::ddl::{
4040
pub use self::operator::{BinaryOperator, UnaryOperator};
4141
pub use self::query::{
4242
Cte, CteAsMaterialized, Distinct, ExceptSelectItem, ExcludeSelectItem, Fetch, ForClause,
43-
ForJson, ForXml, GroupByExpr, IdentWithAlias, Join, JoinConstraint, JoinOperator,
44-
JsonTableColumn, JsonTableColumnErrorHandling, LateralView, LockClause, LockType,
43+
ForJson, ForXml, GroupByExpr, IdentWithAlias, IlikeSelectItem, Join, JoinConstraint,
44+
JoinOperator, JsonTableColumn, JsonTableColumnErrorHandling, LateralView, LockClause, LockType,
4545
NamedWindowDefinition, NonBlock, Offset, OffsetRows, OrderByExpr, Query, RenameSelectItem,
4646
ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SetOperator,
4747
SetQuantifier, Table, TableAlias, TableFactor, TableVersion, TableWithJoins, Top, TopQuantity,

src/ast/query.rs

+29
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,9 @@ impl fmt::Display for IdentWithAlias {
474474
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
475475
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
476476
pub struct WildcardAdditionalOptions {
477+
/// `[ILIKE...]`.
478+
/// Snowflake syntax: <https://docs.snowflake.com/en/sql-reference/sql/select>
479+
pub opt_ilike: Option<IlikeSelectItem>,
477480
/// `[EXCLUDE...]`.
478481
pub opt_exclude: Option<ExcludeSelectItem>,
479482
/// `[EXCEPT...]`.
@@ -489,6 +492,9 @@ pub struct WildcardAdditionalOptions {
489492

490493
impl fmt::Display for WildcardAdditionalOptions {
491494
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
495+
if let Some(ilike) = &self.opt_ilike {
496+
write!(f, " {ilike}")?;
497+
}
492498
if let Some(exclude) = &self.opt_exclude {
493499
write!(f, " {exclude}")?;
494500
}
@@ -505,6 +511,29 @@ impl fmt::Display for WildcardAdditionalOptions {
505511
}
506512
}
507513

514+
/// Snowflake `ILIKE` information.
515+
///
516+
/// # Syntax
517+
/// ```plaintext
518+
/// ILIKE <value>
519+
/// ```
520+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
521+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
522+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
523+
pub struct IlikeSelectItem {
524+
pub pattern: String,
525+
}
526+
527+
impl fmt::Display for IlikeSelectItem {
528+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
529+
write!(
530+
f,
531+
"ILIKE '{}'",
532+
value::escape_single_quote_string(&self.pattern)
533+
)?;
534+
Ok(())
535+
}
536+
}
508537
/// Snowflake `EXCLUDE` information.
509538
///
510539
/// # Syntax

src/parser/mod.rs

+27-1
Original file line numberDiff line numberDiff line change
@@ -9018,7 +9018,13 @@ impl<'a> Parser<'a> {
90189018
pub fn parse_wildcard_additional_options(
90199019
&mut self,
90209020
) -> Result<WildcardAdditionalOptions, ParserError> {
9021-
let opt_exclude = if dialect_of!(self is GenericDialect | DuckDbDialect | SnowflakeDialect)
9021+
let opt_ilike = if dialect_of!(self is GenericDialect | SnowflakeDialect) {
9022+
self.parse_optional_select_item_ilike()?
9023+
} else {
9024+
None
9025+
};
9026+
let opt_exclude = if opt_ilike.is_none()
9027+
&& dialect_of!(self is GenericDialect | DuckDbDialect | SnowflakeDialect)
90229028
{
90239029
self.parse_optional_select_item_exclude()?
90249030
} else {
@@ -9044,13 +9050,33 @@ impl<'a> Parser<'a> {
90449050
};
90459051

90469052
Ok(WildcardAdditionalOptions {
9053+
opt_ilike,
90479054
opt_exclude,
90489055
opt_except,
90499056
opt_rename,
90509057
opt_replace,
90519058
})
90529059
}
90539060

9061+
/// Parse an [`Ilike`](IlikeSelectItem) information for wildcard select items.
9062+
///
9063+
/// If it is not possible to parse it, will return an option.
9064+
pub fn parse_optional_select_item_ilike(
9065+
&mut self,
9066+
) -> Result<Option<IlikeSelectItem>, ParserError> {
9067+
let opt_ilike = if self.parse_keyword(Keyword::ILIKE) {
9068+
let next_token = self.next_token();
9069+
let pattern = match next_token.token {
9070+
Token::SingleQuotedString(s) => s,
9071+
_ => return self.expected("ilike pattern", next_token),
9072+
};
9073+
Some(IlikeSelectItem { pattern })
9074+
} else {
9075+
None
9076+
};
9077+
Ok(opt_ilike)
9078+
}
9079+
90549080
/// Parse an [`Exclude`](ExcludeSelectItem) information for wildcard select items.
90559081
///
90569082
/// If it is not possible to parse it, will return an option.

tests/sqlparser_common.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6622,6 +6622,7 @@ fn lateral_function() {
66226622
distinct: None,
66236623
top: None,
66246624
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
6625+
opt_ilike: None,
66256626
opt_exclude: None,
66266627
opt_except: None,
66276628
opt_rename: None,

tests/sqlparser_duckdb.rs

+2
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ fn test_select_union_by_name() {
148148
distinct: None,
149149
top: None,
150150
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
151+
opt_ilike: None,
151152
opt_exclude: None,
152153
opt_except: None,
153154
opt_rename: None,
@@ -183,6 +184,7 @@ fn test_select_union_by_name() {
183184
distinct: None,
184185
top: None,
185186
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
187+
opt_ilike: None,
186188
opt_exclude: None,
187189
opt_except: None,
188190
opt_rename: None,

tests/sqlparser_snowflake.rs

+39
Original file line numberDiff line numberDiff line change
@@ -1555,3 +1555,42 @@ fn parse_comma_outer_join() {
15551555
fn test_sf_trailing_commas() {
15561556
snowflake().verified_only_select_with_canonical("SELECT 1, 2, FROM t", "SELECT 1, 2 FROM t");
15571557
}
1558+
1559+
#[test]
1560+
fn test_select_wildcard_with_ilike() {
1561+
let select = snowflake_and_generic().verified_only_select(r#"SELECT * ILIKE '%id%' FROM tbl"#);
1562+
let expected = SelectItem::Wildcard(WildcardAdditionalOptions {
1563+
opt_ilike: Some(IlikeSelectItem {
1564+
pattern: "%id%".to_owned(),
1565+
}),
1566+
..Default::default()
1567+
});
1568+
assert_eq!(expected, select.projection[0]);
1569+
}
1570+
1571+
#[test]
1572+
fn test_select_wildcard_with_ilike_double_quote() {
1573+
let res = snowflake().parse_sql_statements(r#"SELECT * ILIKE "%id" FROM tbl"#);
1574+
assert_eq!(
1575+
res.unwrap_err().to_string(),
1576+
"sql parser error: Expected ilike pattern, found: \"%id\""
1577+
);
1578+
}
1579+
1580+
#[test]
1581+
fn test_select_wildcard_with_ilike_number() {
1582+
let res = snowflake().parse_sql_statements(r#"SELECT * ILIKE 42 FROM tbl"#);
1583+
assert_eq!(
1584+
res.unwrap_err().to_string(),
1585+
"sql parser error: Expected ilike pattern, found: 42"
1586+
);
1587+
}
1588+
1589+
#[test]
1590+
fn test_select_wildcard_with_ilike_replace() {
1591+
let res = snowflake().parse_sql_statements(r#"SELECT * ILIKE '%id%' EXCLUDE col FROM tbl"#);
1592+
assert_eq!(
1593+
res.unwrap_err().to_string(),
1594+
"sql parser error: Expected end of statement, found: EXCLUDE"
1595+
);
1596+
}

0 commit comments

Comments
 (0)