mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-12-23 11:12:51 +00:00
Add support for SEMANTIC_VIEW table factor (#2009)
This commit is contained in:
parent
e9eee00ed9
commit
6e80e5c237
7 changed files with 308 additions and 3 deletions
|
|
@ -1410,6 +1410,31 @@ pub enum TableFactor {
|
|||
/// The alias for the table.
|
||||
alias: Option<TableAlias>,
|
||||
},
|
||||
/// Snowflake's SEMANTIC_VIEW function for semantic models.
|
||||
///
|
||||
/// <https://docs.snowflake.com/en/sql-reference/constructs/semantic_view>
|
||||
///
|
||||
/// ```sql
|
||||
/// SELECT * FROM SEMANTIC_VIEW(
|
||||
/// tpch_analysis
|
||||
/// DIMENSIONS customer.customer_market_segment
|
||||
/// METRICS orders.order_average_value
|
||||
/// );
|
||||
/// ```
|
||||
SemanticView {
|
||||
/// The name of the semantic model
|
||||
name: ObjectName,
|
||||
/// List of dimensions or expression referring to dimensions (e.g. DATE_PART('year', col))
|
||||
dimensions: Vec<Expr>,
|
||||
/// List of metrics (references to objects like orders.value, value, orders.*)
|
||||
metrics: Vec<ObjectName>,
|
||||
/// List of facts or expressions referring to facts or dimensions.
|
||||
facts: Vec<Expr>,
|
||||
/// WHERE clause for filtering
|
||||
where_clause: Option<Expr>,
|
||||
/// The alias for the table
|
||||
alias: Option<TableAlias>,
|
||||
},
|
||||
}
|
||||
|
||||
/// The table sample modifier options
|
||||
|
|
@ -2112,6 +2137,40 @@ impl fmt::Display for TableFactor {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
TableFactor::SemanticView {
|
||||
name,
|
||||
dimensions,
|
||||
metrics,
|
||||
facts,
|
||||
where_clause,
|
||||
alias,
|
||||
} => {
|
||||
write!(f, "SEMANTIC_VIEW({name}")?;
|
||||
|
||||
if !dimensions.is_empty() {
|
||||
write!(f, " DIMENSIONS {}", display_comma_separated(dimensions))?;
|
||||
}
|
||||
|
||||
if !metrics.is_empty() {
|
||||
write!(f, " METRICS {}", display_comma_separated(metrics))?;
|
||||
}
|
||||
|
||||
if !facts.is_empty() {
|
||||
write!(f, " FACTS {}", display_comma_separated(facts))?;
|
||||
}
|
||||
|
||||
if let Some(where_clause) = where_clause {
|
||||
write!(f, " WHERE {where_clause}")?;
|
||||
}
|
||||
|
||||
write!(f, ")")?;
|
||||
|
||||
if let Some(alias) = alias {
|
||||
write!(f, " AS {alias}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2044,6 +2044,23 @@ impl Spanned for TableFactor {
|
|||
.chain(symbols.iter().map(|i| i.span()))
|
||||
.chain(alias.as_ref().map(|i| i.span())),
|
||||
),
|
||||
TableFactor::SemanticView {
|
||||
name,
|
||||
dimensions,
|
||||
metrics,
|
||||
facts,
|
||||
where_clause,
|
||||
alias,
|
||||
} => union_spans(
|
||||
name.0
|
||||
.iter()
|
||||
.map(|i| i.span())
|
||||
.chain(dimensions.iter().map(|d| d.span()))
|
||||
.chain(metrics.iter().map(|m| m.span()))
|
||||
.chain(facts.iter().map(|f| f.span()))
|
||||
.chain(where_clause.as_ref().map(|e| e.span()))
|
||||
.chain(alias.as_ref().map(|a| a.span())),
|
||||
),
|
||||
TableFactor::OpenJsonTable { .. } => Span::empty(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1182,6 +1182,20 @@ pub trait Dialect: Debug + Any {
|
|||
fn supports_create_table_like_parenthesized(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if the dialect supports `SEMANTIC_VIEW()` table functions.
|
||||
///
|
||||
/// ```sql
|
||||
/// SELECT * FROM SEMANTIC_VIEW(
|
||||
/// model_name
|
||||
/// DIMENSIONS customer.name, customer.region
|
||||
/// METRICS orders.revenue, orders.count
|
||||
/// WHERE customer.active = true
|
||||
/// )
|
||||
/// ```
|
||||
fn supports_semantic_view_table_factor(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents the operators for which precedence must be defined
|
||||
|
|
|
|||
|
|
@ -566,6 +566,10 @@ impl Dialect for SnowflakeDialect {
|
|||
fn supports_select_wildcard_exclude(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_semantic_view_table_factor(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// Peeks ahead to identify tokens that are expected after
|
||||
|
|
|
|||
|
|
@ -290,6 +290,7 @@ define_keywords!(
|
|||
DETACH,
|
||||
DETAIL,
|
||||
DETERMINISTIC,
|
||||
DIMENSIONS,
|
||||
DIRECTORY,
|
||||
DISABLE,
|
||||
DISCARD,
|
||||
|
|
@ -359,6 +360,7 @@ define_keywords!(
|
|||
EXTERNAL,
|
||||
EXTERNAL_VOLUME,
|
||||
EXTRACT,
|
||||
FACTS,
|
||||
FAIL,
|
||||
FAILOVER,
|
||||
FALSE,
|
||||
|
|
@ -566,6 +568,7 @@ define_keywords!(
|
|||
METADATA,
|
||||
METHOD,
|
||||
METRIC,
|
||||
METRICS,
|
||||
MICROSECOND,
|
||||
MICROSECONDS,
|
||||
MILLENIUM,
|
||||
|
|
@ -828,6 +831,7 @@ define_keywords!(
|
|||
SECURITY,
|
||||
SEED,
|
||||
SELECT,
|
||||
SEMANTIC_VIEW,
|
||||
SEMI,
|
||||
SENSITIVE,
|
||||
SEPARATOR,
|
||||
|
|
|
|||
|
|
@ -4245,6 +4245,18 @@ impl<'a> Parser<'a> {
|
|||
/// not be efficient as it does a loop on the tokens with `peek_nth_token`
|
||||
/// each time.
|
||||
pub fn parse_keyword_with_tokens(&mut self, expected: Keyword, tokens: &[Token]) -> bool {
|
||||
self.keyword_with_tokens(expected, tokens, true)
|
||||
}
|
||||
|
||||
/// Peeks to see if the current token is the `expected` keyword followed by specified tokens
|
||||
/// without consuming them.
|
||||
///
|
||||
/// See [Self::parse_keyword_with_tokens] for details.
|
||||
pub(crate) fn peek_keyword_with_tokens(&mut self, expected: Keyword, tokens: &[Token]) -> bool {
|
||||
self.keyword_with_tokens(expected, tokens, false)
|
||||
}
|
||||
|
||||
fn keyword_with_tokens(&mut self, expected: Keyword, tokens: &[Token], consume: bool) -> bool {
|
||||
match &self.peek_token_ref().token {
|
||||
Token::Word(w) if expected == w.keyword => {
|
||||
for (idx, token) in tokens.iter().enumerate() {
|
||||
|
|
@ -4252,10 +4264,13 @@ impl<'a> Parser<'a> {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
// consume all tokens
|
||||
for _ in 0..(tokens.len() + 1) {
|
||||
self.advance_token();
|
||||
|
||||
if consume {
|
||||
for _ in 0..(tokens.len() + 1) {
|
||||
self.advance_token();
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
|
|
@ -13397,6 +13412,7 @@ impl<'a> Parser<'a> {
|
|||
| TableFactor::Pivot { alias, .. }
|
||||
| TableFactor::Unpivot { alias, .. }
|
||||
| TableFactor::MatchRecognize { alias, .. }
|
||||
| TableFactor::SemanticView { alias, .. }
|
||||
| TableFactor::NestedJoin { alias, .. } => {
|
||||
// but not `FROM (mytable AS alias1) AS alias2`.
|
||||
if let Some(inner_alias) = alias {
|
||||
|
|
@ -13511,6 +13527,10 @@ impl<'a> Parser<'a> {
|
|||
} else if self.parse_keyword_with_tokens(Keyword::XMLTABLE, &[Token::LParen]) {
|
||||
self.prev_token();
|
||||
self.parse_xml_table_factor()
|
||||
} else if self.dialect.supports_semantic_view_table_factor()
|
||||
&& self.peek_keyword_with_tokens(Keyword::SEMANTIC_VIEW, &[Token::LParen])
|
||||
{
|
||||
self.parse_semantic_view_table_factor()
|
||||
} else {
|
||||
let name = self.parse_object_name(true)?;
|
||||
|
||||
|
|
@ -13842,6 +13862,70 @@ impl<'a> Parser<'a> {
|
|||
Ok(XmlPassingClause { arguments })
|
||||
}
|
||||
|
||||
/// Parse a [TableFactor::SemanticView]
|
||||
fn parse_semantic_view_table_factor(&mut self) -> Result<TableFactor, ParserError> {
|
||||
self.expect_keyword(Keyword::SEMANTIC_VIEW)?;
|
||||
self.expect_token(&Token::LParen)?;
|
||||
|
||||
let name = self.parse_object_name(true)?;
|
||||
|
||||
// Parse DIMENSIONS, METRICS, FACTS and WHERE clauses in flexible order
|
||||
let mut dimensions = Vec::new();
|
||||
let mut metrics = Vec::new();
|
||||
let mut facts = Vec::new();
|
||||
let mut where_clause = None;
|
||||
|
||||
while self.peek_token().token != Token::RParen {
|
||||
if self.parse_keyword(Keyword::DIMENSIONS) {
|
||||
if !dimensions.is_empty() {
|
||||
return Err(ParserError::ParserError(
|
||||
"DIMENSIONS clause can only be specified once".to_string(),
|
||||
));
|
||||
}
|
||||
dimensions = self.parse_comma_separated(Parser::parse_expr)?;
|
||||
} else if self.parse_keyword(Keyword::METRICS) {
|
||||
if !metrics.is_empty() {
|
||||
return Err(ParserError::ParserError(
|
||||
"METRICS clause can only be specified once".to_string(),
|
||||
));
|
||||
}
|
||||
metrics = self.parse_comma_separated(|parser| parser.parse_object_name(true))?;
|
||||
} else if self.parse_keyword(Keyword::FACTS) {
|
||||
if !facts.is_empty() {
|
||||
return Err(ParserError::ParserError(
|
||||
"FACTS clause can only be specified once".to_string(),
|
||||
));
|
||||
}
|
||||
facts = self.parse_comma_separated(Parser::parse_expr)?;
|
||||
} else if self.parse_keyword(Keyword::WHERE) {
|
||||
if where_clause.is_some() {
|
||||
return Err(ParserError::ParserError(
|
||||
"WHERE clause can only be specified once".to_string(),
|
||||
));
|
||||
}
|
||||
where_clause = Some(self.parse_expr()?);
|
||||
} else {
|
||||
return parser_err!(
|
||||
"Expected one of DIMENSIONS, METRICS, FACTS or WHERE",
|
||||
self.peek_token().span.start
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.expect_token(&Token::RParen)?;
|
||||
|
||||
let alias = self.maybe_parse_table_alias()?;
|
||||
|
||||
Ok(TableFactor::SemanticView {
|
||||
name,
|
||||
dimensions,
|
||||
metrics,
|
||||
facts,
|
||||
where_clause,
|
||||
alias,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_match_recognize(&mut self, table: TableFactor) -> Result<TableFactor, ParserError> {
|
||||
self.expect_token(&Token::LParen)?;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue