Skip to content

Commit d9fbf7f

Browse files
wojpadloWojciech Padłoclaude
authored
Task LAV-3307: Streamlit versions — ADD LIVE VERSION / COMMIT / ABORT, SHOW VERSIONS, git-verb and EXECUTE errors (#3700)
Versions are metadata only: a live-version timestamp on the streamlits row and committed versions (VERSION$2+) in a child table; DESC's default/last/live version columns track them. PUSH/PULL report the non-git lineage error, ROOT_LOCATION apps the missing attached stage, EXECUTE STREAMLIT the resolve error. New compat tests pinned by real-Snowflake snapshots. Co-authored-by: Wojciech Padło <wojciech.padlo@localstack.cloud> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 1859c9b commit d9fbf7f

3 files changed

Lines changed: 121 additions & 3 deletions

File tree

‎src/ast/mod.rs‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6255,6 +6255,24 @@ pub enum Statement {
62556255
name: ObjectName,
62566256
},
62576257
/// ```sql
6258+
/// SHOW VERSIONS IN STREAMLIT <name> [ LIMIT <n> ]
6259+
/// ```
6260+
ShowStreamlitVersions {
6261+
/// Streamlit name.
6262+
name: ObjectName,
6263+
/// `LIMIT <n>`.
6264+
limit: Option<Expr>,
6265+
},
6266+
/// ```sql
6267+
/// EXECUTE STREAMLIT <name>( [ <arg> [ , ... ] ] )
6268+
/// ```
6269+
ExecuteStreamlit {
6270+
/// Streamlit name.
6271+
name: ObjectName,
6272+
/// Call arguments (never evaluated).
6273+
args: Vec<Expr>,
6274+
},
6275+
/// ```sql
62586276
/// SHOW [ TERSE ] STREAMLITS [ LIKE '<pattern>' ] [ IN <scope> ]
62596277
/// [ LIMIT <n> [ FROM '<s>' ] ]
62606278
/// ```
@@ -9886,6 +9904,16 @@ impl fmt::Display for Statement {
98869904
Statement::DescribeStreamlit { name } => {
98879905
write!(f, "DESCRIBE STREAMLIT {name}")
98889906
}
9907+
Statement::ShowStreamlitVersions { name, limit } => {
9908+
write!(f, "SHOW VERSIONS IN STREAMLIT {name}")?;
9909+
if let Some(limit) = limit {
9910+
write!(f, " LIMIT {limit}")?;
9911+
}
9912+
Ok(())
9913+
}
9914+
Statement::ExecuteStreamlit { name, args } => {
9915+
write!(f, "EXECUTE STREAMLIT {name}({})", display_comma_separated(args))
9916+
}
98899917
Statement::ShowStreamlits {
98909918
terse,
98919919
show_options,
@@ -15752,12 +15780,32 @@ impl fmt::Display for AlterExternalVolumeOperation {
1575215780
pub enum AlterStreamlitOperation {
1575315781
/// `RENAME TO <new_name>`
1575415782
RenameTo(ObjectName),
15783+
/// `ADD LIVE VERSION FROM LAST`
15784+
AddLiveVersionFromLast,
15785+
/// `COMMIT`
15786+
Commit,
15787+
/// `ABORT`
15788+
Abort,
15789+
/// `PUSH [ TO '<git_branch_uri>' ]`
15790+
Push(Option<String>),
15791+
/// `PULL`
15792+
Pull,
1575515793
}
1575615794

1575715795
impl fmt::Display for AlterStreamlitOperation {
1575815796
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1575915797
match self {
1576015798
AlterStreamlitOperation::RenameTo(name) => write!(f, "RENAME TO {name}"),
15799+
AlterStreamlitOperation::AddLiveVersionFromLast => {
15800+
write!(f, "ADD LIVE VERSION FROM LAST")
15801+
}
15802+
AlterStreamlitOperation::Commit => write!(f, "COMMIT"),
15803+
AlterStreamlitOperation::Abort => write!(f, "ABORT"),
15804+
AlterStreamlitOperation::Push(None) => write!(f, "PUSH"),
15805+
AlterStreamlitOperation::Push(Some(uri)) => {
15806+
write!(f, "PUSH TO '{}'", value::escape_single_quote_string(uri))
15807+
}
15808+
AlterStreamlitOperation::Pull => write!(f, "PULL"),
1576115809
}
1576215810
}
1576315811
}

‎src/ast/spans.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,8 @@ impl Spanned for Statement {
636636
Statement::CreateStreamlit { .. } => Span::empty(),
637637
Statement::DropStreamlit { .. } => Span::empty(),
638638
Statement::DescribeStreamlit { .. } => Span::empty(),
639+
Statement::ShowStreamlitVersions { .. } => Span::empty(),
640+
Statement::ExecuteStreamlit { .. } => Span::empty(),
639641
Statement::ShowStreamlits { .. } => Span::empty(),
640642
Statement::AlterStreamlit { .. } => Span::empty(),
641643
Statement::DescribeSnowflakeSecret { .. } => Span::empty(),

‎src/dialect/snowflake.rs‎

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,10 @@ impl SnowflakeDialect {
249249
return Some(parse_alter_streamlit(parser));
250250
}
251251

252+
if parser.parse_keywords(&[Keyword::EXECUTE, Keyword::STREAMLIT]) {
253+
return Some(parse_execute_streamlit(parser));
254+
}
255+
252256
if parser.parse_keywords(&[Keyword::ALTER, Keyword::FILE, Keyword::FORMAT]) {
253257
// ALTER FILE FORMAT
254258
return Some(parse_alter_file_format(parser));
@@ -956,6 +960,9 @@ impl SnowflakeDialect {
956960
if parser.parse_keyword(Keyword::ACCOUNTS) {
957961
return Some(parse_show_accounts(parser));
958962
}
963+
if parser.parse_keywords(&[Keyword::VERSIONS, Keyword::IN, Keyword::STREAMLIT]) {
964+
return Some(parse_show_streamlit_versions(parser));
965+
}
959966
let terse = parser.parse_keyword(Keyword::TERSE);
960967
if parser.parse_keywords(&[Keyword::DYNAMIC, Keyword::TABLES]) {
961968
return Some(parse_show_objects(terse, true, false, parser));
@@ -6251,19 +6258,80 @@ fn parse_create_streamlit(or_replace: bool, parser: &mut Parser) -> Result<State
62516258
})
62526259
}
62536260

6254-
/// Parse `ALTER STREAMLIT [IF EXISTS] <name> RENAME TO <new_name>`.
6261+
/// Consume the next token when it is the unquoted word `word`.
6262+
fn parse_bare_word(parser: &mut Parser, word: &str) -> bool {
6263+
let matched = matches!(&parser.peek_token_ref().token,
6264+
Token::Word(w) if w.quote_style.is_none() && w.value.eq_ignore_ascii_case(word));
6265+
if matched {
6266+
parser.next_token();
6267+
}
6268+
matched
6269+
}
6270+
6271+
/// Parse `ALTER STREAMLIT [IF EXISTS] <name> RENAME TO <new_name>` and the
6272+
/// version / git verbs (`ADD LIVE VERSION FROM LAST`, `COMMIT`, `ABORT`,
6273+
/// `PUSH [TO '<uri>']`, `PULL`), which take no `IF EXISTS`.
62556274
fn parse_alter_streamlit(parser: &mut Parser) -> Result<Statement, ParserError> {
62566275
let if_exists = parser.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
62576276
let name = parser.parse_object_name(false)?;
6258-
parser.expect_keywords(&[Keyword::RENAME, Keyword::TO])?;
6259-
let operation = AlterStreamlitOperation::RenameTo(parser.parse_object_name(false)?);
6277+
let operation = if if_exists || parser.parse_keyword(Keyword::RENAME) {
6278+
if if_exists {
6279+
parser.expect_keyword_is(Keyword::RENAME)?;
6280+
}
6281+
parser.expect_keyword_is(Keyword::TO)?;
6282+
AlterStreamlitOperation::RenameTo(parser.parse_object_name(false)?)
6283+
} else if parser.parse_keyword(Keyword::ADD) {
6284+
if !parse_bare_word(parser, "LIVE") {
6285+
return parser.expected("LIVE", parser.peek_token());
6286+
}
6287+
parser.expect_keywords(&[Keyword::VERSION, Keyword::FROM, Keyword::LAST])?;
6288+
AlterStreamlitOperation::AddLiveVersionFromLast
6289+
} else if parser.parse_keyword(Keyword::COMMIT) {
6290+
AlterStreamlitOperation::Commit
6291+
} else if parser.parse_keyword(Keyword::ABORT) {
6292+
AlterStreamlitOperation::Abort
6293+
} else if parse_bare_word(parser, "PUSH") {
6294+
let to = if parser.parse_keyword(Keyword::TO) {
6295+
Some(parser.parse_literal_string()?)
6296+
} else {
6297+
None
6298+
};
6299+
AlterStreamlitOperation::Push(to)
6300+
} else if parse_bare_word(parser, "PULL") {
6301+
AlterStreamlitOperation::Pull
6302+
} else {
6303+
return parser.expected(
6304+
"RENAME, ADD, COMMIT, ABORT, PUSH or PULL",
6305+
parser.peek_token(),
6306+
);
6307+
};
62606308
Ok(Statement::AlterStreamlit {
62616309
if_exists,
62626310
name,
62636311
operation,
62646312
})
62656313
}
62666314

6315+
/// Parse `SHOW VERSIONS IN STREAMLIT <name> [LIMIT <n>]`.
6316+
fn parse_show_streamlit_versions(parser: &mut Parser) -> Result<Statement, ParserError> {
6317+
let name = parser.parse_object_name(false)?;
6318+
let limit = if parser.parse_keyword(Keyword::LIMIT) {
6319+
Some(parser.parse_expr()?)
6320+
} else {
6321+
None
6322+
};
6323+
Ok(Statement::ShowStreamlitVersions { name, limit })
6324+
}
6325+
6326+
/// Parse `EXECUTE STREAMLIT <name>( [<arg>, ...] )`.
6327+
fn parse_execute_streamlit(parser: &mut Parser) -> Result<Statement, ParserError> {
6328+
let name = parser.parse_object_name(false)?;
6329+
parser.expect_token(&Token::LParen)?;
6330+
let args = parser.parse_comma_separated0(Parser::parse_expr, Token::RParen)?;
6331+
parser.expect_token(&Token::RParen)?;
6332+
Ok(Statement::ExecuteStreamlit { name, args })
6333+
}
6334+
62676335
/// Parse `DROP STREAMLIT [IF EXISTS] <name>`.
62686336
fn parse_drop_streamlit(parser: &mut Parser) -> Result<Statement, ParserError> {
62696337
let if_exists = parser.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);

0 commit comments

Comments
 (0)