Skip to content
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
59 changes: 59 additions & 0 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(())
}
}
}
}
Expand Down
17 changes: 17 additions & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}
Expand Down
14 changes: 14 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/snowflake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ define_keywords!(
DETACH,
DETAIL,
DETERMINISTIC,
DIMENSIONS,
DIRECTORY,
DISABLE,
DISCARD,
Expand Down Expand Up @@ -359,6 +360,7 @@ define_keywords!(
EXTERNAL,
EXTERNAL_VOLUME,
EXTRACT,
FACTS,
FAIL,
FAILOVER,
FALSE,
Expand Down Expand Up @@ -566,6 +568,7 @@ define_keywords!(
METADATA,
METHOD,
METRIC,
METRICS,
MICROSECOND,
MICROSECONDS,
MILLENIUM,
Expand Down Expand Up @@ -828,6 +831,7 @@ define_keywords!(
SECURITY,
SEED,
SELECT,
SEMANTIC_VIEW,
SEMI,
SENSITIVE,
SEPARATOR,
Expand Down
90 changes: 87 additions & 3 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4245,17 +4245,32 @@ 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() {
if self.peek_nth_token_ref(idx + 1).token != *token {
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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)?;

Expand Down Expand Up @@ -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)?;

Expand Down
Loading