Skip to content

Commit 29882fe

Browse files
feat: impliement select * ilike for snowflake
1 parent 2f03fad commit 29882fe

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
@@ -9041,7 +9041,13 @@ impl<'a> Parser<'a> {
90419041
pub fn parse_wildcard_additional_options(
90429042
&mut self,
90439043
) -> Result<WildcardAdditionalOptions, ParserError> {
9044-
let opt_exclude = if dialect_of!(self is GenericDialect | DuckDbDialect | SnowflakeDialect)
9044+
let opt_ilike = if dialect_of!(self is GenericDialect | SnowflakeDialect) {
9045+
self.parse_optional_select_item_ilike()?
9046+
} else {
9047+
None
9048+
};
9049+
let opt_exclude = if opt_ilike.is_none()
9050+
&& dialect_of!(self is GenericDialect | DuckDbDialect | SnowflakeDialect)
90459051
{
90469052
self.parse_optional_select_item_exclude()?
90479053
} else {
@@ -9067,13 +9073,33 @@ impl<'a> Parser<'a> {
90679073
};
90689074

90699075
Ok(WildcardAdditionalOptions {
9076+
opt_ilike,
90709077
opt_exclude,
90719078
opt_except,
90729079
opt_rename,
90739080
opt_replace,
90749081
})
90759082
}
90769083

9084+
/// Parse an [`Ilike`](IlikeSelectItem) information for wildcard select items.
9085+
///
9086+
/// If it is not possible to parse it, will return an option.
9087+
pub fn parse_optional_select_item_ilike(
9088+
&mut self,
9089+
) -> Result<Option<IlikeSelectItem>, ParserError> {
9090+
let opt_ilike = if self.parse_keyword(Keyword::ILIKE) {
9091+
let next_token = self.next_token();
9092+
let pattern = match next_token.token {
9093+
Token::SingleQuotedString(s) => s,
9094+
_ => return self.expected("ilike pattern", next_token),
9095+
};
9096+
Some(IlikeSelectItem { pattern })
9097+
} else {
9098+
None
9099+
};
9100+
Ok(opt_ilike)
9101+
}
9102+
90779103
/// Parse an [`Exclude`](ExcludeSelectItem) information for wildcard select items.
90789104
///
90799105
/// 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
@@ -6611,6 +6611,7 @@ fn lateral_function() {
66116611
distinct: None,
66126612
top: None,
66136613
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
6614+
opt_ilike: None,
66146615
opt_exclude: None,
66156616
opt_except: None,
66166617
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
@@ -1554,3 +1554,42 @@ fn parse_comma_outer_join() {
15541554
fn test_sf_trailing_commas() {
15551555
snowflake().verified_only_select_with_canonical("SELECT 1, 2, FROM t", "SELECT 1, 2 FROM t");
15561556
}
1557+
1558+
#[test]
1559+
fn test_select_wildcard_with_ilike() {
1560+
let select = snowflake_and_generic().verified_only_select(r#"SELECT * ILIKE '%id%' FROM tbl"#);
1561+
let expected = SelectItem::Wildcard(WildcardAdditionalOptions {
1562+
opt_ilike: Some(IlikeSelectItem {
1563+
pattern: "%id%".to_owned(),
1564+
}),
1565+
..Default::default()
1566+
});
1567+
assert_eq!(expected, select.projection[0]);
1568+
}
1569+
1570+
#[test]
1571+
fn test_select_wildcard_with_ilike_double_quote() {
1572+
let res = snowflake().parse_sql_statements(r#"SELECT * ILIKE "%id" FROM tbl"#);
1573+
assert_eq!(
1574+
res.unwrap_err().to_string(),
1575+
"sql parser error: Expected ilike pattern, found: \"%id\""
1576+
);
1577+
}
1578+
1579+
#[test]
1580+
fn test_select_wildcard_with_ilike_number() {
1581+
let res = snowflake().parse_sql_statements(r#"SELECT * ILIKE 42 FROM tbl"#);
1582+
assert_eq!(
1583+
res.unwrap_err().to_string(),
1584+
"sql parser error: Expected ilike pattern, found: 42"
1585+
);
1586+
}
1587+
1588+
#[test]
1589+
fn test_select_wildcard_with_ilike_replace() {
1590+
let res = snowflake().parse_sql_statements(r#"SELECT * ILIKE '%id%' EXCLUDE col FROM tbl"#);
1591+
assert_eq!(
1592+
res.unwrap_err().to_string(),
1593+
"sql parser error: Expected end of statement, found: EXCLUDE"
1594+
);
1595+
}

0 commit comments

Comments
 (0)