Skip to content

feat: implement select * ilike for snowflake #1228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ pub use self::ddl::{
pub use self::operator::{BinaryOperator, UnaryOperator};
pub use self::query::{
Cte, CteAsMaterialized, Distinct, ExceptSelectItem, ExcludeSelectItem, Fetch, ForClause,
ForJson, ForXml, GroupByExpr, IdentWithAlias, Join, JoinConstraint, JoinOperator,
JsonTableColumn, JsonTableColumnErrorHandling, LateralView, LockClause, LockType,
ForJson, ForXml, GroupByExpr, IdentWithAlias, IlikeSelectItem, Join, JoinConstraint,
JoinOperator, JsonTableColumn, JsonTableColumnErrorHandling, LateralView, LockClause, LockType,
NamedWindowDefinition, NonBlock, Offset, OffsetRows, OrderByExpr, Query, RenameSelectItem,
ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SetOperator,
SetQuantifier, Table, TableAlias, TableFactor, TableVersion, TableWithJoins, Top, TopQuantity,
Expand Down
29 changes: 29 additions & 0 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,9 @@ impl fmt::Display for IdentWithAlias {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct WildcardAdditionalOptions {
/// `[ILIKE...]`.
/// Snowflake syntax: <https://docs.snowflake.com/en/sql-reference/sql/select>
pub opt_ilike: Option<IlikeSelectItem>,
/// `[EXCLUDE...]`.
pub opt_exclude: Option<ExcludeSelectItem>,
/// `[EXCEPT...]`.
Expand All @@ -489,6 +492,9 @@ pub struct WildcardAdditionalOptions {

impl fmt::Display for WildcardAdditionalOptions {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(ilike) = &self.opt_ilike {
write!(f, " {ilike}")?;
}
if let Some(exclude) = &self.opt_exclude {
write!(f, " {exclude}")?;
}
Expand All @@ -505,6 +511,29 @@ impl fmt::Display for WildcardAdditionalOptions {
}
}

/// Snowflake `ILIKE` information.
///
/// # Syntax
/// ```plaintext
/// ILIKE <value>
/// ```
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct IlikeSelectItem {
pub pattern: String,
}

impl fmt::Display for IlikeSelectItem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"ILIKE '{}'",
value::escape_single_quote_string(&self.pattern)
)?;
Ok(())
}
}
/// Snowflake `EXCLUDE` information.
///
/// # Syntax
Expand Down
28 changes: 27 additions & 1 deletion src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9041,7 +9041,13 @@ impl<'a> Parser<'a> {
pub fn parse_wildcard_additional_options(
&mut self,
) -> Result<WildcardAdditionalOptions, ParserError> {
let opt_exclude = if dialect_of!(self is GenericDialect | DuckDbDialect | SnowflakeDialect)
let opt_ilike = if dialect_of!(self is GenericDialect | SnowflakeDialect) {
self.parse_optional_select_item_ilike()?
} else {
None
};
let opt_exclude = if opt_ilike.is_none()
&& dialect_of!(self is GenericDialect | DuckDbDialect | SnowflakeDialect)
{
self.parse_optional_select_item_exclude()?
} else {
Expand All @@ -9067,13 +9073,33 @@ impl<'a> Parser<'a> {
};

Ok(WildcardAdditionalOptions {
opt_ilike,
opt_exclude,
opt_except,
opt_rename,
opt_replace,
})
}

/// Parse an [`Ilike`](IlikeSelectItem) information for wildcard select items.
///
/// If it is not possible to parse it, will return an option.
pub fn parse_optional_select_item_ilike(
&mut self,
) -> Result<Option<IlikeSelectItem>, ParserError> {
let opt_ilike = if self.parse_keyword(Keyword::ILIKE) {
let next_token = self.next_token();
let pattern = match next_token.token {
Token::SingleQuotedString(s) => s,
_ => return self.expected("ilike pattern", next_token),
};
Some(IlikeSelectItem { pattern })
} else {
None
};
Ok(opt_ilike)
}

/// Parse an [`Exclude`](ExcludeSelectItem) information for wildcard select items.
///
/// If it is not possible to parse it, will return an option.
Expand Down
1 change: 1 addition & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6611,6 +6611,7 @@ fn lateral_function() {
distinct: None,
top: None,
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
opt_ilike: None,
opt_exclude: None,
opt_except: None,
opt_rename: None,
Expand Down
2 changes: 2 additions & 0 deletions tests/sqlparser_duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ fn test_select_union_by_name() {
distinct: None,
top: None,
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
opt_ilike: None,
opt_exclude: None,
opt_except: None,
opt_rename: None,
Expand Down Expand Up @@ -183,6 +184,7 @@ fn test_select_union_by_name() {
distinct: None,
top: None,
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
opt_ilike: None,
opt_exclude: None,
opt_except: None,
opt_rename: None,
Expand Down
39 changes: 39 additions & 0 deletions tests/sqlparser_snowflake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1554,3 +1554,42 @@ fn parse_comma_outer_join() {
fn test_sf_trailing_commas() {
snowflake().verified_only_select_with_canonical("SELECT 1, 2, FROM t", "SELECT 1, 2 FROM t");
}

#[test]
fn test_select_wildcard_with_ilike() {
let select = snowflake_and_generic().verified_only_select(r#"SELECT * ILIKE '%id%' FROM tbl"#);
let expected = SelectItem::Wildcard(WildcardAdditionalOptions {
opt_ilike: Some(IlikeSelectItem {
pattern: "%id%".to_owned(),
}),
..Default::default()
});
assert_eq!(expected, select.projection[0]);
}

#[test]
fn test_select_wildcard_with_ilike_double_quote() {
let res = snowflake().parse_sql_statements(r#"SELECT * ILIKE "%id" FROM tbl"#);
assert_eq!(
res.unwrap_err().to_string(),
"sql parser error: Expected ilike pattern, found: \"%id\""
);
}

#[test]
fn test_select_wildcard_with_ilike_number() {
let res = snowflake().parse_sql_statements(r#"SELECT * ILIKE 42 FROM tbl"#);
assert_eq!(
res.unwrap_err().to_string(),
"sql parser error: Expected ilike pattern, found: 42"
);
}

#[test]
fn test_select_wildcard_with_ilike_replace() {
let res = snowflake().parse_sql_statements(r#"SELECT * ILIKE '%id%' EXCLUDE col FROM tbl"#);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 for the negative test case

assert_eq!(
res.unwrap_err().to_string(),
"sql parser error: Expected end of statement, found: EXCLUDE"
);
}
Loading