Skip to content

Commit 6e80e5c

Browse files
authored
Add support for SEMANTIC_VIEW table factor (#2009)
1 parent e9eee00 commit 6e80e5c

File tree

7 files changed

+308
-3
lines changed

7 files changed

+308
-3
lines changed

src/ast/query.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,6 +1410,31 @@ pub enum TableFactor {
14101410
/// The alias for the table.
14111411
alias: Option<TableAlias>,
14121412
},
1413+
/// Snowflake's SEMANTIC_VIEW function for semantic models.
1414+
///
1415+
/// <https://docs.snowflake.com/en/sql-reference/constructs/semantic_view>
1416+
///
1417+
/// ```sql
1418+
/// SELECT * FROM SEMANTIC_VIEW(
1419+
/// tpch_analysis
1420+
/// DIMENSIONS customer.customer_market_segment
1421+
/// METRICS orders.order_average_value
1422+
/// );
1423+
/// ```
1424+
SemanticView {
1425+
/// The name of the semantic model
1426+
name: ObjectName,
1427+
/// List of dimensions or expression referring to dimensions (e.g. DATE_PART('year', col))
1428+
dimensions: Vec<Expr>,
1429+
/// List of metrics (references to objects like orders.value, value, orders.*)
1430+
metrics: Vec<ObjectName>,
1431+
/// List of facts or expressions referring to facts or dimensions.
1432+
facts: Vec<Expr>,
1433+
/// WHERE clause for filtering
1434+
where_clause: Option<Expr>,
1435+
/// The alias for the table
1436+
alias: Option<TableAlias>,
1437+
},
14131438
}
14141439

14151440
/// The table sample modifier options
@@ -2112,6 +2137,40 @@ impl fmt::Display for TableFactor {
21122137
}
21132138
Ok(())
21142139
}
2140+
TableFactor::SemanticView {
2141+
name,
2142+
dimensions,
2143+
metrics,
2144+
facts,
2145+
where_clause,
2146+
alias,
2147+
} => {
2148+
write!(f, "SEMANTIC_VIEW({name}")?;
2149+
2150+
if !dimensions.is_empty() {
2151+
write!(f, " DIMENSIONS {}", display_comma_separated(dimensions))?;
2152+
}
2153+
2154+
if !metrics.is_empty() {
2155+
write!(f, " METRICS {}", display_comma_separated(metrics))?;
2156+
}
2157+
2158+
if !facts.is_empty() {
2159+
write!(f, " FACTS {}", display_comma_separated(facts))?;
2160+
}
2161+
2162+
if let Some(where_clause) = where_clause {
2163+
write!(f, " WHERE {where_clause}")?;
2164+
}
2165+
2166+
write!(f, ")")?;
2167+
2168+
if let Some(alias) = alias {
2169+
write!(f, " AS {alias}")?;
2170+
}
2171+
2172+
Ok(())
2173+
}
21152174
}
21162175
}
21172176
}

src/ast/spans.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2044,6 +2044,23 @@ impl Spanned for TableFactor {
20442044
.chain(symbols.iter().map(|i| i.span()))
20452045
.chain(alias.as_ref().map(|i| i.span())),
20462046
),
2047+
TableFactor::SemanticView {
2048+
name,
2049+
dimensions,
2050+
metrics,
2051+
facts,
2052+
where_clause,
2053+
alias,
2054+
} => union_spans(
2055+
name.0
2056+
.iter()
2057+
.map(|i| i.span())
2058+
.chain(dimensions.iter().map(|d| d.span()))
2059+
.chain(metrics.iter().map(|m| m.span()))
2060+
.chain(facts.iter().map(|f| f.span()))
2061+
.chain(where_clause.as_ref().map(|e| e.span()))
2062+
.chain(alias.as_ref().map(|a| a.span())),
2063+
),
20472064
TableFactor::OpenJsonTable { .. } => Span::empty(),
20482065
}
20492066
}

src/dialect/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,6 +1182,20 @@ pub trait Dialect: Debug + Any {
11821182
fn supports_create_table_like_parenthesized(&self) -> bool {
11831183
false
11841184
}
1185+
1186+
/// Returns true if the dialect supports `SEMANTIC_VIEW()` table functions.
1187+
///
1188+
/// ```sql
1189+
/// SELECT * FROM SEMANTIC_VIEW(
1190+
/// model_name
1191+
/// DIMENSIONS customer.name, customer.region
1192+
/// METRICS orders.revenue, orders.count
1193+
/// WHERE customer.active = true
1194+
/// )
1195+
/// ```
1196+
fn supports_semantic_view_table_factor(&self) -> bool {
1197+
false
1198+
}
11851199
}
11861200

11871201
/// This represents the operators for which precedence must be defined

src/dialect/snowflake.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,10 @@ impl Dialect for SnowflakeDialect {
566566
fn supports_select_wildcard_exclude(&self) -> bool {
567567
true
568568
}
569+
570+
fn supports_semantic_view_table_factor(&self) -> bool {
571+
true
572+
}
569573
}
570574

571575
// Peeks ahead to identify tokens that are expected after

src/keywords.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ define_keywords!(
290290
DETACH,
291291
DETAIL,
292292
DETERMINISTIC,
293+
DIMENSIONS,
293294
DIRECTORY,
294295
DISABLE,
295296
DISCARD,
@@ -359,6 +360,7 @@ define_keywords!(
359360
EXTERNAL,
360361
EXTERNAL_VOLUME,
361362
EXTRACT,
363+
FACTS,
362364
FAIL,
363365
FAILOVER,
364366
FALSE,
@@ -566,6 +568,7 @@ define_keywords!(
566568
METADATA,
567569
METHOD,
568570
METRIC,
571+
METRICS,
569572
MICROSECOND,
570573
MICROSECONDS,
571574
MILLENIUM,
@@ -828,6 +831,7 @@ define_keywords!(
828831
SECURITY,
829832
SEED,
830833
SELECT,
834+
SEMANTIC_VIEW,
831835
SEMI,
832836
SENSITIVE,
833837
SEPARATOR,

src/parser/mod.rs

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4245,17 +4245,32 @@ impl<'a> Parser<'a> {
42454245
/// not be efficient as it does a loop on the tokens with `peek_nth_token`
42464246
/// each time.
42474247
pub fn parse_keyword_with_tokens(&mut self, expected: Keyword, tokens: &[Token]) -> bool {
4248+
self.keyword_with_tokens(expected, tokens, true)
4249+
}
4250+
4251+
/// Peeks to see if the current token is the `expected` keyword followed by specified tokens
4252+
/// without consuming them.
4253+
///
4254+
/// See [Self::parse_keyword_with_tokens] for details.
4255+
pub(crate) fn peek_keyword_with_tokens(&mut self, expected: Keyword, tokens: &[Token]) -> bool {
4256+
self.keyword_with_tokens(expected, tokens, false)
4257+
}
4258+
4259+
fn keyword_with_tokens(&mut self, expected: Keyword, tokens: &[Token], consume: bool) -> bool {
42484260
match &self.peek_token_ref().token {
42494261
Token::Word(w) if expected == w.keyword => {
42504262
for (idx, token) in tokens.iter().enumerate() {
42514263
if self.peek_nth_token_ref(idx + 1).token != *token {
42524264
return false;
42534265
}
42544266
}
4255-
// consume all tokens
4256-
for _ in 0..(tokens.len() + 1) {
4257-
self.advance_token();
4267+
4268+
if consume {
4269+
for _ in 0..(tokens.len() + 1) {
4270+
self.advance_token();
4271+
}
42584272
}
4273+
42594274
true
42604275
}
42614276
_ => false,
@@ -13397,6 +13412,7 @@ impl<'a> Parser<'a> {
1339713412
| TableFactor::Pivot { alias, .. }
1339813413
| TableFactor::Unpivot { alias, .. }
1339913414
| TableFactor::MatchRecognize { alias, .. }
13415+
| TableFactor::SemanticView { alias, .. }
1340013416
| TableFactor::NestedJoin { alias, .. } => {
1340113417
// but not `FROM (mytable AS alias1) AS alias2`.
1340213418
if let Some(inner_alias) = alias {
@@ -13511,6 +13527,10 @@ impl<'a> Parser<'a> {
1351113527
} else if self.parse_keyword_with_tokens(Keyword::XMLTABLE, &[Token::LParen]) {
1351213528
self.prev_token();
1351313529
self.parse_xml_table_factor()
13530+
} else if self.dialect.supports_semantic_view_table_factor()
13531+
&& self.peek_keyword_with_tokens(Keyword::SEMANTIC_VIEW, &[Token::LParen])
13532+
{
13533+
self.parse_semantic_view_table_factor()
1351413534
} else {
1351513535
let name = self.parse_object_name(true)?;
1351613536

@@ -13842,6 +13862,70 @@ impl<'a> Parser<'a> {
1384213862
Ok(XmlPassingClause { arguments })
1384313863
}
1384413864

13865+
/// Parse a [TableFactor::SemanticView]
13866+
fn parse_semantic_view_table_factor(&mut self) -> Result<TableFactor, ParserError> {
13867+
self.expect_keyword(Keyword::SEMANTIC_VIEW)?;
13868+
self.expect_token(&Token::LParen)?;
13869+
13870+
let name = self.parse_object_name(true)?;
13871+
13872+
// Parse DIMENSIONS, METRICS, FACTS and WHERE clauses in flexible order
13873+
let mut dimensions = Vec::new();
13874+
let mut metrics = Vec::new();
13875+
let mut facts = Vec::new();
13876+
let mut where_clause = None;
13877+
13878+
while self.peek_token().token != Token::RParen {
13879+
if self.parse_keyword(Keyword::DIMENSIONS) {
13880+
if !dimensions.is_empty() {
13881+
return Err(ParserError::ParserError(
13882+
"DIMENSIONS clause can only be specified once".to_string(),
13883+
));
13884+
}
13885+
dimensions = self.parse_comma_separated(Parser::parse_expr)?;
13886+
} else if self.parse_keyword(Keyword::METRICS) {
13887+
if !metrics.is_empty() {
13888+
return Err(ParserError::ParserError(
13889+
"METRICS clause can only be specified once".to_string(),
13890+
));
13891+
}
13892+
metrics = self.parse_comma_separated(|parser| parser.parse_object_name(true))?;
13893+
} else if self.parse_keyword(Keyword::FACTS) {
13894+
if !facts.is_empty() {
13895+
return Err(ParserError::ParserError(
13896+
"FACTS clause can only be specified once".to_string(),
13897+
));
13898+
}
13899+
facts = self.parse_comma_separated(Parser::parse_expr)?;
13900+
} else if self.parse_keyword(Keyword::WHERE) {
13901+
if where_clause.is_some() {
13902+
return Err(ParserError::ParserError(
13903+
"WHERE clause can only be specified once".to_string(),
13904+
));
13905+
}
13906+
where_clause = Some(self.parse_expr()?);
13907+
} else {
13908+
return parser_err!(
13909+
"Expected one of DIMENSIONS, METRICS, FACTS or WHERE",
13910+
self.peek_token().span.start
13911+
)?;
13912+
}
13913+
}
13914+
13915+
self.expect_token(&Token::RParen)?;
13916+
13917+
let alias = self.maybe_parse_table_alias()?;
13918+
13919+
Ok(TableFactor::SemanticView {
13920+
name,
13921+
dimensions,
13922+
metrics,
13923+
facts,
13924+
where_clause,
13925+
alias,
13926+
})
13927+
}
13928+
1384513929
fn parse_match_recognize(&mut self, table: TableFactor) -> Result<TableFactor, ParserError> {
1384613930
self.expect_token(&Token::LParen)?;
1384713931

0 commit comments

Comments
 (0)