Skip to content

Commit 5da702f

Browse files
authored
Add support for Snowflake AT/BEFORE (apache#1667)
1 parent 44df6d6 commit 5da702f

File tree

7 files changed

+48
-12
lines changed

7 files changed

+48
-12
lines changed

src/ast/query.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1873,13 +1873,19 @@ impl fmt::Display for TableAliasColumnDef {
18731873
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18741874
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
18751875
pub enum TableVersion {
1876+
/// When the table version is defined using `FOR SYSTEM_TIME AS OF`.
1877+
/// For example: `SELECT * FROM tbl FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)`
18761878
ForSystemTimeAsOf(Expr),
1879+
/// When the table version is defined using a function.
1880+
/// For example: `SELECT * FROM tbl AT(TIMESTAMP => '2020-08-14 09:30:00')`
1881+
Function(Expr),
18771882
}
18781883

18791884
impl Display for TableVersion {
18801885
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
18811886
match self {
18821887
TableVersion::ForSystemTimeAsOf(e) => write!(f, " FOR SYSTEM_TIME AS OF {e}")?,
1888+
TableVersion::Function(func) => write!(f, " {func}")?,
18831889
}
18841890
Ok(())
18851891
}

src/dialect/bigquery.rs

+5
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,9 @@ impl Dialect for BigQueryDialect {
7777
fn supports_struct_literal(&self) -> bool {
7878
true
7979
}
80+
81+
// See <https://cloud.google.com/bigquery/docs/access-historical-data>
82+
fn supports_timestamp_versioning(&self) -> bool {
83+
true
84+
}
8085
}

src/dialect/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,12 @@ pub trait Dialect: Debug + Any {
834834
fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, _parser: &mut Parser) -> bool {
835835
explicit || !keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw)
836836
}
837+
838+
/// Returns true if this dialect supports querying historical table data
839+
/// by specifying which version of the data to query.
840+
fn supports_timestamp_versioning(&self) -> bool {
841+
false
842+
}
837843
}
838844

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

src/dialect/mssql.rs

+5
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,9 @@ impl Dialect for MsSqlDialect {
9090
fn supports_set_stmt_without_operator(&self) -> bool {
9191
true
9292
}
93+
94+
/// See: <https://learn.microsoft.com/en-us/sql/relational-databases/tables/querying-data-in-a-system-versioned-temporal-table>
95+
fn supports_timestamp_versioning(&self) -> bool {
96+
true
97+
}
9398
}

src/dialect/snowflake.rs

+5
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,11 @@ impl Dialect for SnowflakeDialect {
296296
_ => true,
297297
}
298298
}
299+
300+
/// See: <https://docs.snowflake.com/en/sql-reference/constructs/at-before>
301+
fn supports_timestamp_versioning(&self) -> bool {
302+
true
303+
}
299304
}
300305

301306
fn parse_file_staging_command(kw: Keyword, parser: &mut Parser) -> Result<Statement, ParserError> {

src/parser/mod.rs

+14-12
Original file line numberDiff line numberDiff line change
@@ -11208,7 +11208,7 @@ impl<'a> Parser<'a> {
1120811208
};
1120911209

1121011210
// Parse potential version qualifier
11211-
let version = self.parse_table_version()?;
11211+
let version = self.maybe_parse_table_version()?;
1121211212

1121311213
// Postgres, MSSQL, ClickHouse: table-valued functions:
1121411214
let args = if self.consume_token(&Token::LParen) {
@@ -11639,18 +11639,20 @@ impl<'a> Parser<'a> {
1163911639
}
1164011640
}
1164111641

11642-
/// Parse a given table version specifier.
11643-
///
11644-
/// For now it only supports timestamp versioning for BigQuery and MSSQL dialects.
11645-
pub fn parse_table_version(&mut self) -> Result<Option<TableVersion>, ParserError> {
11646-
if dialect_of!(self is BigQueryDialect | MsSqlDialect)
11647-
&& self.parse_keywords(&[Keyword::FOR, Keyword::SYSTEM_TIME, Keyword::AS, Keyword::OF])
11648-
{
11649-
let expr = self.parse_expr()?;
11650-
Ok(Some(TableVersion::ForSystemTimeAsOf(expr)))
11651-
} else {
11652-
Ok(None)
11642+
/// Parses a the timestamp version specifier (i.e. query historical data)
11643+
pub fn maybe_parse_table_version(&mut self) -> Result<Option<TableVersion>, ParserError> {
11644+
if self.dialect.supports_timestamp_versioning() {
11645+
if self.parse_keywords(&[Keyword::FOR, Keyword::SYSTEM_TIME, Keyword::AS, Keyword::OF])
11646+
{
11647+
let expr = self.parse_expr()?;
11648+
return Ok(Some(TableVersion::ForSystemTimeAsOf(expr)));
11649+
} else if self.peek_keyword(Keyword::AT) || self.peek_keyword(Keyword::BEFORE) {
11650+
let func_name = self.parse_object_name(true)?;
11651+
let func = self.parse_function(func_name)?;
11652+
return Ok(Some(TableVersion::Function(func)));
11653+
}
1165311654
}
11655+
Ok(None)
1165411656
}
1165511657

1165611658
/// Parses MySQL's JSON_TABLE column definition.

tests/sqlparser_snowflake.rs

+7
Original file line numberDiff line numberDiff line change
@@ -3051,3 +3051,10 @@ fn test_sql_keywords_as_select_item_aliases() {
30513051
.is_err());
30523052
}
30533053
}
3054+
3055+
#[test]
3056+
fn test_timetravel_at_before() {
3057+
snowflake().verified_only_select("SELECT * FROM tbl AT(TIMESTAMP => '2024-12-15 00:00:00')");
3058+
snowflake()
3059+
.verified_only_select("SELECT * FROM tbl BEFORE(TIMESTAMP => '2024-12-15 00:00:00')");
3060+
}

0 commit comments

Comments
 (0)