@@ -1946,6 +1946,23 @@ def remove_comments(sql_query: str, uuids: List[str]) -> str:
19461946 )
19471947
19481948
1949+ def _validate_iceberg_named_version_ref (
1950+ value : Optional [str ], param_name : str
1951+ ) -> Optional [str ]:
1952+ """Validate a non-empty Iceberg tag/branch/version-ref name."""
1953+ if value is None :
1954+ return None
1955+ if not isinstance (value , str ):
1956+ raise ValueError (
1957+ f"'{ param_name } ' must be a string Iceberg name, "
1958+ f"got { type (value ).__name__ } ."
1959+ )
1960+ stripped = value .strip ()
1961+ if not stripped :
1962+ raise ValueError (f"'{ param_name } ' must be a non-empty Iceberg name." )
1963+ return stripped
1964+
1965+
19491966class TimeTravelConfig (NamedTuple ):
19501967 """Configuration for time travel operations."""
19511968
@@ -1957,6 +1974,8 @@ class TimeTravelConfig(NamedTuple):
19571974 stream : Optional [str ] = None
19581975 version : Optional [int ] = None
19591976 version_tag : Optional [str ] = None
1977+ version_ref : Optional [str ] = None
1978+ branch : Optional [str ] = None
19601979
19611980 @staticmethod
19621981 def validate_and_normalize_params (
@@ -1968,6 +1987,8 @@ def validate_and_normalize_params(
19681987 stream : Optional [str ] = None ,
19691988 version : Optional [int ] = None ,
19701989 version_tag : Optional [str ] = None ,
1990+ version_ref : Optional [str ] = None ,
1991+ branch : Optional [str ] = None ,
19711992 ) -> Optional ["TimeTravelConfig" ]:
19721993 """
19731994 Validates and normalizes time travel parameters.
@@ -1989,9 +2010,31 @@ def validate_and_normalize_params(
19892010 Raises:
19902011 ValueError: If parameters are invalid.
19912012 """
2013+ version_tag = _validate_iceberg_named_version_ref (version_tag , "version_tag" )
2014+ version_ref = _validate_iceberg_named_version_ref (version_ref , "version_ref" )
2015+ branch = _validate_iceberg_named_version_ref (branch , "branch" )
2016+
2017+ named_ref_count = sum (
2018+ arg is not None for arg in (version_tag , version_ref , branch )
2019+ )
2020+ if named_ref_count > 1 :
2021+ raise ValueError (
2022+ "Exactly one of 'version_tag', 'version_ref', or 'branch' may be "
2023+ "provided for Iceberg named-ref time travel."
2024+ )
2025+
19922026 time_travel_arg_count = sum (
19932027 arg is not None
1994- for arg in (statement , offset , timestamp , stream , version , version_tag )
2028+ for arg in (
2029+ statement ,
2030+ offset ,
2031+ timestamp ,
2032+ stream ,
2033+ version ,
2034+ version_tag ,
2035+ version_ref ,
2036+ branch ,
2037+ )
19952038 )
19962039
19972040 # Validate mode
@@ -2026,32 +2069,25 @@ def validate_and_normalize_params(
20262069 f"'version' must be an int Iceberg snapshot id, got { type (version ).__name__ } ."
20272070 )
20282071
2029- # version_tag (Iceberg tag name, mapped to Snowflake's
2030- # ``AT(VERSION_TAG => '<name>')`` grammar) only works with 'at' mode —
2031- # Iceberg tag reads are positional (bound to a specific snapshot),
2032- # not range-of-time, so ``BEFORE`` has no meaning.
2033- if version_tag is not None and time_travel_mode .lower () != "at" :
2034- raise ValueError (
2035- "Iceberg version_tag time travel can only be used with "
2036- "time_travel_mode='at', not 'before'."
2037- )
2038-
2039- # Validate version_tag type — Iceberg tag names are strings. Empty
2040- # strings are invalid.
2041- if version_tag is not None :
2042- if not isinstance (version_tag , str ):
2072+ # Named Iceberg refs (tag / branch / version_ref) map to Snowflake's
2073+ # ``AT(VERSION_REF => '<name>')`` grammar and only work with 'at' mode.
2074+ for param_name , param_value in (
2075+ ("version_tag" , version_tag ),
2076+ ("version_ref" , version_ref ),
2077+ ("branch" , branch ),
2078+ ):
2079+ if param_value is not None and time_travel_mode .lower () != "at" :
20432080 raise ValueError (
2044- f"'version_tag' must be a string Iceberg tag name, "
2045- f"got { type ( version_tag ). __name__ } ."
2081+ f"Iceberg { param_name } time travel can only be used with "
2082+ "time_travel_mode='at', not 'before' ."
20462083 )
2047- if not version_tag :
2048- raise ValueError ("'version_tag' must be a non-empty Iceberg tag name." )
20492084
20502085 # Validate exactly one parameter is provided
20512086 if time_travel_arg_count != 1 :
20522087 raise ValueError (
20532088 "Exactly one of 'statement', 'offset', 'timestamp', 'stream', "
2054- "'version', or 'version_tag' must be provided."
2089+ "'version', 'version_tag', 'version_ref', or 'branch' must be "
2090+ "provided."
20552091 )
20562092
20572093 # Normalize timestamp
@@ -2087,6 +2123,8 @@ def validate_and_normalize_params(
20872123 stream = stream ,
20882124 version = version ,
20892125 version_tag = version_tag ,
2126+ version_ref = version_ref ,
2127+ branch = branch ,
20902128 )
20912129
20922130 def generate_sql_clause (self ) -> str :
@@ -2097,11 +2135,12 @@ def generate_sql_clause(self) -> str:
20972135 Returns:
20982136 SQL clause like " AT (TIMESTAMP => TO_TIMESTAMP_NTZ('...'))",
20992137 " AT (VERSION => 1234567890)" for Iceberg snapshot id time travel,
2100- or " AT (VERSION_TAG => 'release_v1 ')" for Iceberg tag time
2101- travel.
2138+ or " AT (VERSION_REF => 'audit-branch ')" for Iceberg tag/branch
2139+ time travel.
21022140
21032141 Note on escaping: string-valued parameters (``statement``,
2104- ``stream``, ``version_tag``, ``timestamp``) are embedded inside
2142+ ``stream``, ``version_tag``, ``version_ref``, ``branch``,
2143+ ``timestamp``) are embedded inside
21052144 single-quoted SQL literals via the existing ``str_to_sql``
21062145 helper in ``analyzer.datatype_mapper`` so embedded ``'``, ``\\ ``
21072146 and newline characters are properly escaped. This keeps the
@@ -2128,8 +2167,12 @@ def generate_sql_clause(self) -> str:
21282167 clause += f"(STREAM => { str_to_sql (self .stream )} )"
21292168 elif self .version is not None :
21302169 clause += f"(VERSION => { self .version } )"
2170+ elif self .version_ref is not None :
2171+ clause += f"(VERSION_REF => { str_to_sql (self .version_ref )} )"
2172+ elif self .branch is not None :
2173+ clause += f"(VERSION_REF => { str_to_sql (self .branch )} )"
21312174 elif self .version_tag is not None :
2132- clause += f"(VERSION_TAG => { str_to_sql (self .version_tag )} )"
2175+ clause += f"(VERSION_REF => { str_to_sql (self .version_tag )} )"
21332176 elif self .timestamp is not None :
21342177 if self .timestamp_type is not None :
21352178 timestamp_type = self .timestamp_type .upper ()
0 commit comments