feat: implement select * ilike for snowflake (#1228)

This commit is contained in:
Hiranmaya Gundu 2024-04-21 05:22:08 -07:00 committed by GitHub
parent d1f67bdc47
commit 4604628c43
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 100 additions and 3 deletions

View file

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

View file

@ -474,6 +474,9 @@ impl fmt::Display for IdentWithAlias {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct WildcardAdditionalOptions { pub struct WildcardAdditionalOptions {
/// `[ILIKE...]`.
/// Snowflake syntax: <https://docs.snowflake.com/en/sql-reference/sql/select>
pub opt_ilike: Option<IlikeSelectItem>,
/// `[EXCLUDE...]`. /// `[EXCLUDE...]`.
pub opt_exclude: Option<ExcludeSelectItem>, pub opt_exclude: Option<ExcludeSelectItem>,
/// `[EXCEPT...]`. /// `[EXCEPT...]`.
@ -489,6 +492,9 @@ pub struct WildcardAdditionalOptions {
impl fmt::Display for WildcardAdditionalOptions { impl fmt::Display for WildcardAdditionalOptions {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 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 { if let Some(exclude) = &self.opt_exclude {
write!(f, " {exclude}")?; write!(f, " {exclude}")?;
} }
@ -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. /// Snowflake `EXCLUDE` information.
/// ///
/// # Syntax /// # Syntax

View file

@ -9018,7 +9018,13 @@ impl<'a> Parser<'a> {
pub fn parse_wildcard_additional_options( pub fn parse_wildcard_additional_options(
&mut self, &mut self,
) -> Result<WildcardAdditionalOptions, ParserError> { ) -> 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()? self.parse_optional_select_item_exclude()?
} else { } else {
@ -9044,6 +9050,7 @@ impl<'a> Parser<'a> {
}; };
Ok(WildcardAdditionalOptions { Ok(WildcardAdditionalOptions {
opt_ilike,
opt_exclude, opt_exclude,
opt_except, opt_except,
opt_rename, opt_rename,
@ -9051,6 +9058,25 @@ impl<'a> Parser<'a> {
}) })
} }
/// 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. /// Parse an [`Exclude`](ExcludeSelectItem) information for wildcard select items.
/// ///
/// If it is not possible to parse it, will return an option. /// If it is not possible to parse it, will return an option.

View file

@ -6622,6 +6622,7 @@ fn lateral_function() {
distinct: None, distinct: None,
top: None, top: None,
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions { projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
opt_ilike: None,
opt_exclude: None, opt_exclude: None,
opt_except: None, opt_except: None,
opt_rename: None, opt_rename: None,

View file

@ -148,6 +148,7 @@ fn test_select_union_by_name() {
distinct: None, distinct: None,
top: None, top: None,
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions { projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
opt_ilike: None,
opt_exclude: None, opt_exclude: None,
opt_except: None, opt_except: None,
opt_rename: None, opt_rename: None,
@ -183,6 +184,7 @@ fn test_select_union_by_name() {
distinct: None, distinct: None,
top: None, top: None,
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions { projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
opt_ilike: None,
opt_exclude: None, opt_exclude: None,
opt_except: None, opt_except: None,
opt_rename: None, opt_rename: None,

View file

@ -1555,3 +1555,42 @@ fn parse_comma_outer_join() {
fn test_sf_trailing_commas() { fn test_sf_trailing_commas() {
snowflake().verified_only_select_with_canonical("SELECT 1, 2, FROM t", "SELECT 1, 2 FROM t"); 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"#);
assert_eq!(
res.unwrap_err().to_string(),
"sql parser error: Expected end of statement, found: EXCLUDE"
);
}