mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-10-09 13:40:22 +00:00
feat: implement select * ilike for snowflake (#1228)
This commit is contained in:
parent
d1f67bdc47
commit
4604628c43
6 changed files with 100 additions and 3 deletions
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue