Skip to content

Commit d83d99a

Browse files
bombsimonayman-sigma
authored andcommitted
Add support for SEMANTIC_VIEW table factor (apache#2009)
1 parent 75fd2e8 commit d83d99a

7 files changed

Lines changed: 308 additions & 3 deletions

File tree

src/ast/query.rs

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

14211446
/// The table sample modifier options
@@ -2125,6 +2150,40 @@ impl fmt::Display for TableFactor {
21252150
}
21262151
Ok(())
21272152
}
2153+
TableFactor::SemanticView {
2154+
name,
2155+
dimensions,
2156+
metrics,
2157+
facts,
2158+
where_clause,
2159+
alias,
2160+
} => {
2161+
write!(f, "SEMANTIC_VIEW({name}")?;
2162+
2163+
if !dimensions.is_empty() {
2164+
write!(f, " DIMENSIONS {}", display_comma_separated(dimensions))?;
2165+
}
2166+
2167+
if !metrics.is_empty() {
2168+
write!(f, " METRICS {}", display_comma_separated(metrics))?;
2169+
}
2170+
2171+
if !facts.is_empty() {
2172+
write!(f, " FACTS {}", display_comma_separated(facts))?;
2173+
}
2174+
2175+
if let Some(where_clause) = where_clause {
2176+
write!(f, " WHERE {where_clause}")?;
2177+
}
2178+
2179+
write!(f, ")")?;
2180+
2181+
if let Some(alias) = alias {
2182+
write!(f, " AS {alias}")?;
2183+
}
2184+
2185+
Ok(())
2186+
}
21282187
}
21292188
}
21302189
}

src/ast/spans.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2053,6 +2053,23 @@ impl Spanned for TableFactor {
20532053
.chain(symbols.iter().map(|i| i.span()))
20542054
.chain(alias.as_ref().map(|i| i.span())),
20552055
),
2056+
TableFactor::SemanticView {
2057+
name,
2058+
dimensions,
2059+
metrics,
2060+
facts,
2061+
where_clause,
2062+
alias,
2063+
} => union_spans(
2064+
name.0
2065+
.iter()
2066+
.map(|i| i.span())
2067+
.chain(dimensions.iter().map(|d| d.span()))
2068+
.chain(metrics.iter().map(|m| m.span()))
2069+
.chain(facts.iter().map(|f| f.span()))
2070+
.chain(where_clause.as_ref().map(|e| e.span()))
2071+
.chain(alias.as_ref().map(|a| a.span())),
2072+
),
20562073
TableFactor::OpenJsonTable { .. } => Span::empty(),
20572074
}
20582075
}

src/dialect/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,20 @@ pub trait Dialect: Debug + Any {
11871187
fn supports_create_table_like_parenthesized(&self) -> bool {
11881188
false
11891189
}
1190+
1191+
/// Returns true if the dialect supports `SEMANTIC_VIEW()` table functions.
1192+
///
1193+
/// ```sql
1194+
/// SELECT * FROM SEMANTIC_VIEW(
1195+
/// model_name
1196+
/// DIMENSIONS customer.name, customer.region
1197+
/// METRICS orders.revenue, orders.count
1198+
/// WHERE customer.active = true
1199+
/// )
1200+
/// ```
1201+
fn supports_semantic_view_table_factor(&self) -> bool {
1202+
false
1203+
}
11901204
}
11911205

11921206
/// 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
@@ -4276,17 +4276,32 @@ impl<'a> Parser<'a> {
42764276
/// not be efficient as it does a loop on the tokens with `peek_nth_token`
42774277
/// each time.
42784278
pub fn parse_keyword_with_tokens(&mut self, expected: Keyword, tokens: &[Token]) -> bool {
4279+
self.keyword_with_tokens(expected, tokens, true)
4280+
}
4281+
4282+
/// Peeks to see if the current token is the `expected` keyword followed by specified tokens
4283+
/// without consuming them.
4284+
///
4285+
/// See [Self::parse_keyword_with_tokens] for details.
4286+
pub(crate) fn peek_keyword_with_tokens(&mut self, expected: Keyword, tokens: &[Token]) -> bool {
4287+
self.keyword_with_tokens(expected, tokens, false)
4288+
}
4289+
4290+
fn keyword_with_tokens(&mut self, expected: Keyword, tokens: &[Token], consume: bool) -> bool {
42794291
match &self.peek_token_ref().token {
42804292
Token::Word(w) if expected == w.keyword => {
42814293
for (idx, token) in tokens.iter().enumerate() {
42824294
if self.peek_nth_token_ref(idx + 1).token != *token {
42834295
return false;
42844296
}
42854297
}
4286-
// consume all tokens
4287-
for _ in 0..(tokens.len() + 1) {
4288-
self.advance_token();
4298+
4299+
if consume {
4300+
for _ in 0..(tokens.len() + 1) {
4301+
self.advance_token();
4302+
}
42894303
}
4304+
42904305
true
42914306
}
42924307
_ => false,
@@ -13428,6 +13443,7 @@ impl<'a> Parser<'a> {
1342813443
| TableFactor::Pivot { alias, .. }
1342913444
| TableFactor::Unpivot { alias, .. }
1343013445
| TableFactor::MatchRecognize { alias, .. }
13446+
| TableFactor::SemanticView { alias, .. }
1343113447
| TableFactor::NestedJoin { alias, .. }
1343213448
| TableFactor::PassThroughQuery { alias, .. } => {
1343313449
// but not `FROM (mytable AS alias1) AS alias2`.
@@ -13543,6 +13559,10 @@ impl<'a> Parser<'a> {
1354313559
} else if self.parse_keyword_with_tokens(Keyword::XMLTABLE, &[Token::LParen]) {
1354413560
self.prev_token();
1354513561
self.parse_xml_table_factor()
13562+
} else if self.dialect.supports_semantic_view_table_factor()
13563+
&& self.peek_keyword_with_tokens(Keyword::SEMANTIC_VIEW, &[Token::LParen])
13564+
{
13565+
self.parse_semantic_view_table_factor()
1354613566
} else {
1354713567
let name = self.parse_object_name(true)?;
1354813568

@@ -13874,6 +13894,70 @@ impl<'a> Parser<'a> {
1387413894
Ok(XmlPassingClause { arguments })
1387513895
}
1387613896

13897+
/// Parse a [TableFactor::SemanticView]
13898+
fn parse_semantic_view_table_factor(&mut self) -> Result<TableFactor, ParserError> {
13899+
self.expect_keyword(Keyword::SEMANTIC_VIEW)?;
13900+
self.expect_token(&Token::LParen)?;
13901+
13902+
let name = self.parse_object_name(true)?;
13903+
13904+
// Parse DIMENSIONS, METRICS, FACTS and WHERE clauses in flexible order
13905+
let mut dimensions = Vec::new();
13906+
let mut metrics = Vec::new();
13907+
let mut facts = Vec::new();
13908+
let mut where_clause = None;
13909+
13910+
while self.peek_token().token != Token::RParen {
13911+
if self.parse_keyword(Keyword::DIMENSIONS) {
13912+
if !dimensions.is_empty() {
13913+
return Err(ParserError::ParserError(
13914+
"DIMENSIONS clause can only be specified once".to_string(),
13915+
));
13916+
}
13917+
dimensions = self.parse_comma_separated(Parser::parse_expr)?;
13918+
} else if self.parse_keyword(Keyword::METRICS) {
13919+
if !metrics.is_empty() {
13920+
return Err(ParserError::ParserError(
13921+
"METRICS clause can only be specified once".to_string(),
13922+
));
13923+
}
13924+
metrics = self.parse_comma_separated(|parser| parser.parse_object_name(true))?;
13925+
} else if self.parse_keyword(Keyword::FACTS) {
13926+
if !facts.is_empty() {
13927+
return Err(ParserError::ParserError(
13928+
"FACTS clause can only be specified once".to_string(),
13929+
));
13930+
}
13931+
facts = self.parse_comma_separated(Parser::parse_expr)?;
13932+
} else if self.parse_keyword(Keyword::WHERE) {
13933+
if where_clause.is_some() {
13934+
return Err(ParserError::ParserError(
13935+
"WHERE clause can only be specified once".to_string(),
13936+
));
13937+
}
13938+
where_clause = Some(self.parse_expr()?);
13939+
} else {
13940+
return parser_err!(
13941+
"Expected one of DIMENSIONS, METRICS, FACTS or WHERE",
13942+
self.peek_token().span.start
13943+
)?;
13944+
}
13945+
}
13946+
13947+
self.expect_token(&Token::RParen)?;
13948+
13949+
let alias = self.maybe_parse_table_alias()?;
13950+
13951+
Ok(TableFactor::SemanticView {
13952+
name,
13953+
dimensions,
13954+
metrics,
13955+
facts,
13956+
where_clause,
13957+
alias,
13958+
})
13959+
}
13960+
1387713961
fn parse_match_recognize(&mut self, table: TableFactor) -> Result<TableFactor, ParserError> {
1387813962
self.expect_token(&Token::LParen)?;
1387913963

0 commit comments

Comments
 (0)