diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 2ef3a460b..14a247d2e 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2772,41 +2772,65 @@ pub enum Statement { /// ```sql /// SHOW COLUMNS /// ``` - /// - /// Note: this is a MySQL-specific statement. ShowColumns { extended: bool, full: bool, - #[cfg_attr(feature = "visitor", visit(with = "visit_relation"))] - table_name: ObjectName, + show_in: Option, filter: Option, + filter_position: ShowStatementFilterPosition, }, /// ```sql - /// SHOW DATABASES [LIKE 'pattern'] + /// SHOW DATABASES /// ``` - ShowDatabases { filter: Option }, + ShowDatabases { + terse: bool, + history: bool, + filter: Option, + show_in: Option, + starts_with: Option, + limit: Option, + from: Option, + }, /// ```sql - /// SHOW SCHEMAS [LIKE 'pattern'] + /// SHOW SCHEMAS /// ``` - ShowSchemas { filter: Option }, + ShowSchemas { + terse: bool, + history: bool, + filter: Option, + show_in: Option, + starts_with: Option, + limit: Option, + from: Option, + }, /// ```sql /// SHOW TABLES /// ``` ShowTables { + terse: bool, + history: bool, extended: bool, full: bool, - clause: Option, - db_name: Option, + external: bool, filter: Option, + show_in: Option, + starts_with: Option, + limit: Option, + from: Option, + filter_position: ShowStatementFilterPosition, }, /// ```sql /// SHOW VIEWS /// ``` ShowViews { + terse: bool, materialized: bool, - clause: Option, - db_name: Option, filter: Option, + show_in: Option, + starts_with: Option, + limit: Option, + from: Option, + filter_position: ShowStatementFilterPosition, }, /// ```sql /// SHOW COLLATION @@ -4382,79 +4406,226 @@ impl fmt::Display for Statement { Statement::ShowColumns { extended, full, - table_name, + show_in, filter, + filter_position, } => { write!( f, - "SHOW {extended}{full}COLUMNS FROM {table_name}", + "SHOW {extended}{full}COLUMNS", extended = if *extended { "EXTENDED " } else { "" }, full = if *full { "FULL " } else { "" }, - table_name = table_name, )?; - if let Some(filter) = filter { - write!(f, " {filter}")?; + if filter_position == &ShowStatementFilterPosition::InTheMiddle { + if let Some(filter) = filter { + write!(f, " {filter}")?; + } + if let Some(show_in) = show_in { + write!(f, " {show_in}")?; + } + } + if filter_position == &ShowStatementFilterPosition::AtTheEnd { + if let Some(show_in) = show_in { + write!(f, " {show_in}")?; + } + if let Some(filter) = filter { + write!(f, " {filter}")?; + } } Ok(()) } - Statement::ShowDatabases { filter } => { - write!(f, "SHOW DATABASES")?; - if let Some(filter) = filter { - write!(f, " {filter}")?; - } + Statement::ShowDatabases { + terse, + history, + filter, + show_in, + starts_with, + limit, + from, + } => { + write!( + f, + "SHOW {terse}DATABASES", + terse = if *terse { "TERSE " } else { "" } + )?; + write!( + f, + "{history}{filter}{show_in}{starts_with}{limit}{from}", + history = if *history { " HISTORY" } else { "" }, + filter = match filter.as_ref() { + Some(l) => format!(" {l}"), + None => String::new(), + }, + show_in = match show_in { + Some(i) => format!(" {i}"), + None => String::new(), + }, + starts_with = match starts_with.as_ref() { + Some(s) => format!(" STARTS WITH {s}"), + None => String::new(), + }, + limit = match limit.as_ref() { + Some(l) => format!(" LIMIT {l}"), + None => String::new(), + }, + from = match from.as_ref() { + Some(f) => format!(" FROM {f}"), + None => String::new(), + } + )?; Ok(()) } - Statement::ShowSchemas { filter } => { - write!(f, "SHOW SCHEMAS")?; - if let Some(filter) = filter { - write!(f, " {filter}")?; - } + Statement::ShowSchemas { + terse, + history, + filter, + show_in, + starts_with, + limit, + from, + } => { + write!( + f, + "SHOW {terse}SCHEMAS", + terse = if *terse { "TERSE " } else { "" } + )?; + write!( + f, + "{history}{filter}{show_in}{starts_with}{limit}{from}", + history = if *history { " HISTORY" } else { "" }, + filter = match filter.as_ref() { + Some(l) => format!(" {l}"), + None => String::new(), + }, + show_in = match show_in { + Some(i) => format!(" {i}"), + None => String::new(), + }, + starts_with = match starts_with.as_ref() { + Some(s) => format!(" STARTS WITH {s}"), + None => String::new(), + }, + limit = match limit.as_ref() { + Some(l) => format!(" LIMIT {l}"), + None => String::new(), + }, + from = match from.as_ref() { + Some(f) => format!(" FROM {f}"), + None => String::new(), + } + )?; Ok(()) } Statement::ShowTables { + terse, + history, extended, full, - clause: show_clause, - db_name, + external, filter, + show_in, + starts_with, + limit, + from, + filter_position, } => { write!( f, - "SHOW {extended}{full}TABLES", + "SHOW {terse}{extended}{full}{external}TABLES", + terse = if *terse { "TERSE " } else { "" }, extended = if *extended { "EXTENDED " } else { "" }, full = if *full { "FULL " } else { "" }, + external = if *external { "EXTERNAL " } else { "" }, )?; - if let Some(show_clause) = show_clause { - write!(f, " {show_clause}")?; - } - if let Some(db_name) = db_name { - write!(f, " {db_name}")?; - } - if let Some(filter) = filter { - write!(f, " {filter}")?; - } - Ok(()) + write!( + f, + "{history}{like_in_the_middle}{show_in}{starts_with}{limit}{from}{like_at_the_end}", + history = if *history { " HISTORY" } else { "" }, + like_in_the_middle = if *filter_position + == ShowStatementFilterPosition::InTheMiddle + && filter.is_some() + { + format!(" {}", filter.as_ref().unwrap()) + } else { + String::new() + }, + show_in = match show_in { + Some(i) => format!(" {i}"), + None => String::new(), + }, + starts_with = match starts_with.as_ref() { + Some(s) => format!(" STARTS WITH {s}"), + None => String::new(), + }, + limit = match limit.as_ref() { + Some(l) => format!(" LIMIT {l}"), + None => String::new(), + }, + from = match from.as_ref() { + Some(f) => format!(" FROM {f}"), + None => String::new(), + }, + like_at_the_end = if *filter_position + == ShowStatementFilterPosition::AtTheEnd + && filter.is_some() + { + format!(" {}", filter.as_ref().unwrap()) + } else { + String::new() + }, + ) } Statement::ShowViews { + terse, materialized, - clause: show_clause, - db_name, filter, + show_in, + starts_with, + limit, + from, + filter_position, } => { write!( f, - "SHOW {}VIEWS", - if *materialized { "MATERIALIZED " } else { "" } + "SHOW {terse}{materialized}VIEWS", + terse = if *terse { "TERSE " } else { "" }, + materialized = if *materialized { "MATERIALIZED " } else { "" } + )?; + write!( + f, + "{like_in_the_middle}{show_in}{starts_with}{limit}{from}{like_at_the_end}", + like_in_the_middle = if *filter_position + == ShowStatementFilterPosition::InTheMiddle + && filter.is_some() + { + format!(" {}", filter.as_ref().unwrap()) + } else { + String::new() + }, + show_in = match show_in { + Some(i) => format!(" {i}"), + None => String::new(), + }, + starts_with = match starts_with.as_ref() { + Some(s) => format!(" STARTS WITH {s}"), + None => String::new(), + }, + limit = match limit.as_ref() { + Some(l) => format!(" LIMIT {l}"), + None => String::new(), + }, + from = match from.as_ref() { + Some(f) => format!(" FROM {f}"), + None => String::new(), + }, + like_at_the_end = if *filter_position == ShowStatementFilterPosition::AtTheEnd + && filter.is_some() + { + format!(" {}", filter.as_ref().unwrap()) + } else { + String::new() + } )?; - if let Some(show_clause) = show_clause { - write!(f, " {show_clause}")?; - } - if let Some(db_name) = db_name { - write!(f, " {db_name}")?; - } - if let Some(filter) = filter { - write!(f, " {filter}")?; - } Ok(()) } Statement::ShowFunctions { filter } => { @@ -6160,14 +6331,14 @@ impl fmt::Display for ShowStatementFilter { #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub enum ShowClause { +pub enum ShowStatementInClause { IN, FROM, } -impl fmt::Display for ShowClause { +impl fmt::Display for ShowStatementInClause { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use ShowClause::*; + use ShowStatementInClause::*; match self { FROM => write!(f, "FROM"), IN => write!(f, "IN"), @@ -7345,6 +7516,55 @@ impl Display for UtilityOption { } } +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +pub enum ShowStatementFilterPosition { + InTheMiddle, // Snowflake like + AtTheEnd, // MySQL like +} + +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum ShowStatementInParentType { + Account, + Database, + Schema, + Table, + View, +} + +impl fmt::Display for ShowStatementInParentType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ShowStatementInParentType::Account => write!(f, "ACCOUNT"), + ShowStatementInParentType::Database => write!(f, "DATABASE"), + ShowStatementInParentType::Schema => write!(f, "SCHEMA"), + ShowStatementInParentType::Table => write!(f, "TABLE"), + ShowStatementInParentType::View => write!(f, "VIEW"), + } + } +} + +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct ShowStatementIn { + pub clause: ShowStatementInClause, + pub parent_type: Option, + pub parent_name: Option, +} + +impl fmt::Display for ShowStatementIn { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.clause)?; + if let Some(parent_type) = &self.parent_type { + write!(f, " {}", parent_type)?; + } + if let Some(parent_name) = &self.parent_name { + write!(f, " {}", parent_name)?; + } + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 5abddba38..d3c816c1a 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -600,6 +600,12 @@ pub trait Dialect: Debug + Any { fn supports_notify(&self) -> bool { false } + + /// Returns true if this dialect support the `LIKE 'pattern'` option in + /// a `SHOW` statement before the `IN` option + fn supports_show_like_before_in(&self) -> bool { + false + } } /// This represents the operators for which precedence must be defined diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index d9331d952..45ab33796 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -203,6 +203,12 @@ impl Dialect for SnowflakeDialect { fn allow_extract_single_quotes(&self) -> bool { true } + + /// Snowflake expects the `LIKE` option before the `IN` option, + /// for example: https://docs.snowflake.com/en/sql-reference/sql/show-views#syntax + fn supports_show_like_before_in(&self) -> bool { + true + } } /// Parse snowflake create table statement. diff --git a/src/keywords.rs b/src/keywords.rs index d60227c99..08449f67c 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -76,6 +76,7 @@ define_keywords!( ABS, ABSOLUTE, ACCESS, + ACCOUNT, ACTION, ADD, ADMIN, @@ -709,6 +710,7 @@ define_keywords!( STABLE, STAGE, START, + STARTS, STATEMENT, STATIC, STATISTICS, @@ -745,6 +747,7 @@ define_keywords!( TEMP, TEMPORARY, TERMINATED, + TERSE, TEXT, TEXTFILE, THEN, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index fd7d1c578..6f3dadc4c 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -9587,21 +9587,23 @@ impl<'a> Parser<'a> { } pub fn parse_show(&mut self) -> Result { + let terse = self.parse_keyword(Keyword::TERSE); let extended = self.parse_keyword(Keyword::EXTENDED); let full = self.parse_keyword(Keyword::FULL); let session = self.parse_keyword(Keyword::SESSION); let global = self.parse_keyword(Keyword::GLOBAL); + let external = self.parse_keyword(Keyword::EXTERNAL); if self .parse_one_of_keywords(&[Keyword::COLUMNS, Keyword::FIELDS]) .is_some() { Ok(self.parse_show_columns(extended, full)?) } else if self.parse_keyword(Keyword::TABLES) { - Ok(self.parse_show_tables(extended, full)?) + Ok(self.parse_show_tables(terse, extended, full, external)?) } else if self.parse_keywords(&[Keyword::MATERIALIZED, Keyword::VIEWS]) { - Ok(self.parse_show_views(true)?) + Ok(self.parse_show_views(terse, true)?) } else if self.parse_keyword(Keyword::VIEWS) { - Ok(self.parse_show_views(false)?) + Ok(self.parse_show_views(terse, false)?) } else if self.parse_keyword(Keyword::FUNCTIONS) { Ok(self.parse_show_functions()?) } else if extended || full { @@ -9629,9 +9631,9 @@ impl<'a> Parser<'a> { global, }) } else if self.parse_keyword(Keyword::DATABASES) { - self.parse_show_databases() + self.parse_show_databases(terse) } else if self.parse_keyword(Keyword::SCHEMAS) { - self.parse_show_schemas() + self.parse_show_schemas(terse) } else { Ok(Statement::ShowVariable { variable: self.parse_identifiers()?, @@ -9639,15 +9641,39 @@ impl<'a> Parser<'a> { } } - fn parse_show_databases(&mut self) -> Result { + fn parse_show_databases(&mut self, terse: bool) -> Result { + let history = self.parse_keyword(Keyword::HISTORY); + let filter = self.parse_show_statement_filter()?; + let show_in = self.parse_show_opt_in()?; + let starts_with = self.parse_show_opt_starts_with()?; + let limit = self.parse_show_opt_limit()?; + let from = self.parse_show_opt_from()?; Ok(Statement::ShowDatabases { - filter: self.parse_show_statement_filter()?, + terse, + history, + filter, + show_in, + starts_with, + limit, + from, }) } - fn parse_show_schemas(&mut self) -> Result { + fn parse_show_schemas(&mut self, terse: bool) -> Result { + let history = self.parse_keyword(Keyword::HISTORY); + let filter = self.parse_show_statement_filter()?; + let show_in = self.parse_show_opt_in()?; + let starts_with = self.parse_show_opt_starts_with()?; + let limit = self.parse_show_opt_limit()?; + let from = self.parse_show_opt_from()?; Ok(Statement::ShowSchemas { - filter: self.parse_show_statement_filter()?, + terse, + history, + filter, + show_in, + starts_with, + limit, + from, }) } @@ -9681,58 +9707,95 @@ impl<'a> Parser<'a> { extended: bool, full: bool, ) -> Result { - self.expect_one_of_keywords(&[Keyword::FROM, Keyword::IN])?; - let object_name = self.parse_object_name(false)?; - let table_name = match self.parse_one_of_keywords(&[Keyword::FROM, Keyword::IN]) { - Some(_) => { - let db_name = vec![self.parse_identifier(false)?]; - let ObjectName(table_name) = object_name; - let object_name = db_name.into_iter().chain(table_name).collect(); - ObjectName(object_name) - } - None => object_name, - }; - let filter = self.parse_show_statement_filter()?; + let filter; + let filter_position; + let show_in; + if self.dialect.supports_show_like_before_in() { + filter = self.parse_show_statement_filter()?; + filter_position = ShowStatementFilterPosition::InTheMiddle; + show_in = self.parse_show_opt_in()?; + } else { + show_in = self.parse_show_opt_in()?; + filter = self.parse_show_statement_filter()?; + filter_position = ShowStatementFilterPosition::AtTheEnd; + } Ok(Statement::ShowColumns { extended, full, - table_name, + show_in, filter, + filter_position, }) } - pub fn parse_show_tables( + fn parse_show_tables( &mut self, + terse: bool, extended: bool, full: bool, + external: bool, ) -> Result { - let (clause, db_name) = match self.parse_one_of_keywords(&[Keyword::FROM, Keyword::IN]) { - Some(Keyword::FROM) => (Some(ShowClause::FROM), Some(self.parse_identifier(false)?)), - Some(Keyword::IN) => (Some(ShowClause::IN), Some(self.parse_identifier(false)?)), - _ => (None, None), - }; - let filter = self.parse_show_statement_filter()?; + let history = !external && self.parse_keyword(Keyword::HISTORY); + let filter; + let show_in; + let filter_position; + if self.dialect.supports_show_like_before_in() { + filter = self.parse_show_statement_filter()?; + //YOAV: here we have a problem, the hint is DB-dependent (table in a schemas or some other object) + show_in = self.parse_show_opt_in()?; + filter_position = ShowStatementFilterPosition::InTheMiddle; + } else { + show_in = self.parse_show_opt_in()?; + filter = self.parse_show_statement_filter()?; + filter_position = ShowStatementFilterPosition::AtTheEnd; + } + let starts_with = self.parse_show_opt_starts_with()?; + let limit = self.parse_show_opt_limit()?; + let from = self.parse_show_opt_from()?; Ok(Statement::ShowTables { + terse, + history, extended, full, - clause, - db_name, + external, filter, + show_in, + starts_with, + limit, + from, + filter_position, }) } - fn parse_show_views(&mut self, materialized: bool) -> Result { - let (clause, db_name) = match self.parse_one_of_keywords(&[Keyword::FROM, Keyword::IN]) { - Some(Keyword::FROM) => (Some(ShowClause::FROM), Some(self.parse_identifier(false)?)), - Some(Keyword::IN) => (Some(ShowClause::IN), Some(self.parse_identifier(false)?)), - _ => (None, None), - }; - let filter = self.parse_show_statement_filter()?; + fn parse_show_views( + &mut self, + terse: bool, + materialized: bool, + ) -> Result { + let filter; + let show_in; + let filter_position; + if self.dialect.supports_show_like_before_in() { + filter = self.parse_show_statement_filter()?; + show_in = self.parse_show_opt_in()?; + filter_position = ShowStatementFilterPosition::InTheMiddle; + } else { + show_in = self.parse_show_opt_in()?; + filter = self.parse_show_statement_filter()?; + filter_position = ShowStatementFilterPosition::AtTheEnd; + } + let starts_with = self.parse_show_opt_starts_with()?; + let limit = self.parse_show_opt_limit()?; + let from = self.parse_show_opt_from()?; Ok(Statement::ShowViews { materialized, - clause, - db_name, + terse, filter, + filter_position, + show_in, + starts_with, + limit, + from, }) } @@ -12285,6 +12348,137 @@ impl<'a> Parser<'a> { } false } + + /// Look for an expected keyword, without consuming it + fn peek_keyword(&self, expected: Keyword) -> bool { + match self.peek_token().token { + Token::Word(w) => expected == w.keyword, + _ => false, + } + } + + /// Look for one of expected keyword, without consuming it + fn peek_keywords(&self, expected: &[Keyword]) -> bool { + for kw in expected { + if self.peek_keyword(*kw) { + return true; + } + } + false + } + + fn parse_show_opt_in(&mut self) -> Result, ParserError> { + let clause = match self.parse_one_of_keywords(&[Keyword::FROM, Keyword::IN]) { + Some(Keyword::FROM) => ShowStatementInClause::FROM, + Some(Keyword::IN) => ShowStatementInClause::IN, + _ => return Ok(None), + }; + + if self.parse_keyword(Keyword::DATABASE) { + if self.peek_keywords(&[Keyword::STARTS, Keyword::WITH]) { + Ok(Some(ShowStatementIn { + clause, + parent_type: Some(ShowStatementInParentType::Database), + parent_name: None, + })) + } else { + let parent_name = match self.parse_object_name(false) { + Ok(n) => Some(n), + Err(_) => None, + }; + Ok(Some(ShowStatementIn { + clause, + parent_type: Some(ShowStatementInParentType::Database), + parent_name, + })) + } + } else if self.parse_keyword(Keyword::SCHEMA) { + if self.peek_keywords(&[Keyword::STARTS, Keyword::WITH]) { + Ok(Some(ShowStatementIn { + clause, + parent_type: Some(ShowStatementInParentType::Schema), + parent_name: None, + })) + } else { + let parent_name = match self.parse_object_name(false) { + Ok(n) => Some(n), + Err(_) => None, + }; + Ok(Some(ShowStatementIn { + clause, + parent_type: Some(ShowStatementInParentType::Schema), + parent_name, + })) + } + } else if self.parse_keyword(Keyword::ACCOUNT) { + let parent_name = match self.parse_object_name(false) { + Ok(n) => Some(n), + Err(_) => None, + }; + Ok(Some(ShowStatementIn { + clause, + parent_type: Some(ShowStatementInParentType::Account), + parent_name, + })) + } else if self.parse_keyword(Keyword::TABLE) { + let parent_name = match self.parse_object_name(false) { + Ok(n) => Some(n), + Err(_) => None, + }; + Ok(Some(ShowStatementIn { + clause, + parent_type: Some(ShowStatementInParentType::Table), + parent_name, + })) + } else if self.parse_keyword(Keyword::VIEW) { + let parent_name = match self.parse_object_name(false) { + Ok(n) => Some(n), + Err(_) => None, + }; + Ok(Some(ShowStatementIn { + clause, + parent_type: Some(ShowStatementInParentType::View), + parent_name, + })) + } else { + // Parsing MySQL style FROM tbl_name FROM db_name + // which is equivalent to FROM tbl_name.db_name + let mut parent_name = self.parse_object_name(false)?; + if self + .parse_one_of_keywords(&[Keyword::FROM, Keyword::IN]) + .is_some() + { + parent_name.0.insert(0, self.parse_identifier(false)?); + } + + Ok(Some(ShowStatementIn { + clause, + parent_type: None, + parent_name: Some(parent_name), + })) + } + } + + fn parse_show_opt_starts_with(&mut self) -> Result, ParserError> { + match self.parse_keywords(&[Keyword::STARTS, Keyword::WITH]) { + true => Ok(Some(self.parse_value()?)), + false => Ok(None), + } + } + + fn parse_show_opt_limit(&mut self) -> Result, ParserError> { + match self.parse_keyword(Keyword::LIMIT) { + true => Ok(self.parse_limit()?), + false => Ok(None), + } + } + + fn parse_show_opt_from(&mut self) -> Result, ParserError> { + match self.parse_keyword(Keyword::FROM) { + true => Ok(Some(self.parse_value()?)), + false => Ok(None), + } + } } impl Word { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 334dae2b3..e9d8a7124 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -11385,19 +11385,43 @@ fn test_show_dbs_schemas_tables_views() { verified_stmt("SHOW DATABASES LIKE '%abc'"); verified_stmt("SHOW SCHEMAS"); verified_stmt("SHOW SCHEMAS LIKE '%abc'"); - verified_stmt("SHOW TABLES"); - verified_stmt("SHOW TABLES IN db1"); - verified_stmt("SHOW TABLES IN db1 'abc'"); - verified_stmt("SHOW VIEWS"); - verified_stmt("SHOW VIEWS IN db1"); - verified_stmt("SHOW VIEWS IN db1 'abc'"); - verified_stmt("SHOW VIEWS FROM db1"); - verified_stmt("SHOW VIEWS FROM db1 'abc'"); - verified_stmt("SHOW MATERIALIZED VIEWS"); - verified_stmt("SHOW MATERIALIZED VIEWS IN db1"); - verified_stmt("SHOW MATERIALIZED VIEWS IN db1 'abc'"); - verified_stmt("SHOW MATERIALIZED VIEWS FROM db1"); - verified_stmt("SHOW MATERIALIZED VIEWS FROM db1 'abc'"); + + // SHOW is parsed the same by all dialects except the position of the LIKE option + all_dialects_where(|d| d.supports_show_like_before_in()).verified_stmt("SHOW TABLES"); + all_dialects_where(|d| !d.supports_show_like_before_in()).verified_stmt("SHOW TABLES"); + all_dialects_where(|d| d.supports_show_like_before_in()).verified_stmt("SHOW TABLES IN db1"); + all_dialects_where(|d| !d.supports_show_like_before_in()).verified_stmt("SHOW TABLES IN db1"); + all_dialects_where(|d| !d.supports_show_like_before_in()) + .verified_stmt("SHOW TABLES IN db1 'abc'"); + + all_dialects_where(|d| d.supports_show_like_before_in()).verified_stmt("SHOW VIEWS"); + all_dialects_where(|d| !d.supports_show_like_before_in()).verified_stmt("SHOW VIEWS"); + all_dialects_where(|d| d.supports_show_like_before_in()).verified_stmt("SHOW VIEWS IN db1"); + all_dialects_where(|d| !d.supports_show_like_before_in()) + .verified_stmt("SHOW VIEWS IN db1 'abc'"); + all_dialects_where(|d| d.supports_show_like_before_in()).verified_stmt("SHOW VIEWS FROM db1"); + all_dialects_where(|d| !d.supports_show_like_before_in()).verified_stmt("SHOW VIEWS FROM db1"); + all_dialects_where(|d| !d.supports_show_like_before_in()) + .verified_stmt("SHOW VIEWS FROM db1 'abc'"); + + all_dialects_where(|d| d.supports_show_like_before_in()) + .verified_stmt("SHOW MATERIALIZED VIEWS"); + all_dialects_where(|d| !d.supports_show_like_before_in()) + .verified_stmt("SHOW MATERIALIZED VIEWS"); + all_dialects_where(|d| d.supports_show_like_before_in()) + .verified_stmt("SHOW MATERIALIZED VIEWS IN db1"); + all_dialects_where(|d| !d.supports_show_like_before_in()) + .verified_stmt("SHOW MATERIALIZED VIEWS IN db1"); + all_dialects_where(|d| d.supports_show_like_before_in()) + .verified_stmt("SHOW MATERIALIZED VIEWS FROM db1"); + all_dialects_where(|d| !d.supports_show_like_before_in()) + .verified_stmt("SHOW MATERIALIZED VIEWS FROM db1"); + all_dialects_where(|d| !d.supports_show_like_before_in()) + .verified_stmt("SHOW MATERIALIZED VIEWS IN db1 'abc'"); + all_dialects_where(|d| !d.supports_show_like_before_in()) + .verified_stmt("SHOW MATERIALIZED VIEWS FROM db1"); + all_dialects_where(|d| !d.supports_show_like_before_in()) + .verified_stmt("SHOW MATERIALIZED VIEWS FROM db1 'abc'"); } #[test] diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 4b9354e85..09e7d5213 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -223,14 +223,18 @@ fn parse_flush() { #[test] fn parse_show_columns() { - let table_name = ObjectName(vec![Ident::new("mytable")]); assert_eq!( mysql_and_generic().verified_stmt("SHOW COLUMNS FROM mytable"), Statement::ShowColumns { extended: false, full: false, - table_name: table_name.clone(), + show_in: Some(ShowStatementIn { + clause: ShowStatementInClause::FROM, + parent_type: None, + parent_name: Some(ObjectName(vec![Ident::new("mytable")])), + }), filter: None, + filter_position: ShowStatementFilterPosition::AtTheEnd, } ); assert_eq!( @@ -238,8 +242,13 @@ fn parse_show_columns() { Statement::ShowColumns { extended: false, full: false, - table_name: ObjectName(vec![Ident::new("mydb"), Ident::new("mytable")]), + show_in: Some(ShowStatementIn { + clause: ShowStatementInClause::FROM, + parent_type: None, + parent_name: Some(ObjectName(vec![Ident::new("mydb"), Ident::new("mytable")])), + }), filter: None, + filter_position: ShowStatementFilterPosition::AtTheEnd, } ); assert_eq!( @@ -247,8 +256,13 @@ fn parse_show_columns() { Statement::ShowColumns { extended: true, full: false, - table_name: table_name.clone(), + show_in: Some(ShowStatementIn { + clause: ShowStatementInClause::FROM, + parent_type: None, + parent_name: Some(ObjectName(vec![Ident::new("mytable")])), + }), filter: None, + filter_position: ShowStatementFilterPosition::AtTheEnd, } ); assert_eq!( @@ -256,8 +270,13 @@ fn parse_show_columns() { Statement::ShowColumns { extended: false, full: true, - table_name: table_name.clone(), + show_in: Some(ShowStatementIn { + clause: ShowStatementInClause::FROM, + parent_type: None, + parent_name: Some(ObjectName(vec![Ident::new("mytable")])), + }), filter: None, + filter_position: ShowStatementFilterPosition::AtTheEnd, } ); assert_eq!( @@ -265,8 +284,13 @@ fn parse_show_columns() { Statement::ShowColumns { extended: false, full: false, - table_name: table_name.clone(), + show_in: Some(ShowStatementIn { + clause: ShowStatementInClause::FROM, + parent_type: None, + parent_name: Some(ObjectName(vec![Ident::new("mytable")])), + }), filter: Some(ShowStatementFilter::Like("pattern".into())), + filter_position: ShowStatementFilterPosition::AtTheEnd, } ); assert_eq!( @@ -274,18 +298,23 @@ fn parse_show_columns() { Statement::ShowColumns { extended: false, full: false, - table_name, + show_in: Some(ShowStatementIn { + clause: ShowStatementInClause::FROM, + parent_type: None, + parent_name: Some(ObjectName(vec![Ident::new("mytable")])), + }), filter: Some(ShowStatementFilter::Where( mysql_and_generic().verified_expr("1 = 2") )), + filter_position: ShowStatementFilterPosition::AtTheEnd, } ); mysql_and_generic() .one_statement_parses_to("SHOW FIELDS FROM mytable", "SHOW COLUMNS FROM mytable"); mysql_and_generic() - .one_statement_parses_to("SHOW COLUMNS IN mytable", "SHOW COLUMNS FROM mytable"); + .one_statement_parses_to("SHOW COLUMNS IN mytable", "SHOW COLUMNS IN mytable"); mysql_and_generic() - .one_statement_parses_to("SHOW FIELDS IN mytable", "SHOW COLUMNS FROM mytable"); + .one_statement_parses_to("SHOW FIELDS IN mytable", "SHOW COLUMNS IN mytable"); mysql_and_generic().one_statement_parses_to( "SHOW COLUMNS FROM mytable FROM mydb", "SHOW COLUMNS FROM mydb.mytable", @@ -327,63 +356,103 @@ fn parse_show_tables() { assert_eq!( mysql_and_generic().verified_stmt("SHOW TABLES"), Statement::ShowTables { + terse: false, + history: false, extended: false, full: false, - clause: None, - db_name: None, + external: false, + starts_with: None, + limit: None, + from: None, + show_in: None, filter: None, + filter_position: ShowStatementFilterPosition::AtTheEnd } ); assert_eq!( mysql_and_generic().verified_stmt("SHOW TABLES FROM mydb"), Statement::ShowTables { + terse: false, + history: false, extended: false, full: false, - clause: Some(ShowClause::FROM), - db_name: Some(Ident::new("mydb")), + external: false, + starts_with: None, + limit: None, + from: None, + show_in: Some(ShowStatementIn { + clause: ShowStatementInClause::FROM, + parent_type: None, + parent_name: Some(ObjectName(vec![Ident::new("mydb")])), + }), filter: None, + filter_position: ShowStatementFilterPosition::AtTheEnd } ); assert_eq!( mysql_and_generic().verified_stmt("SHOW EXTENDED TABLES"), Statement::ShowTables { + terse: false, + history: false, extended: true, full: false, - clause: None, - db_name: None, + external: false, + starts_with: None, + limit: None, + from: None, + show_in: None, filter: None, + filter_position: ShowStatementFilterPosition::AtTheEnd } ); assert_eq!( mysql_and_generic().verified_stmt("SHOW FULL TABLES"), Statement::ShowTables { + terse: false, + history: false, extended: false, full: true, - clause: None, - db_name: None, + external: false, + starts_with: None, + limit: None, + from: None, + show_in: None, filter: None, + filter_position: ShowStatementFilterPosition::AtTheEnd } ); assert_eq!( mysql_and_generic().verified_stmt("SHOW TABLES LIKE 'pattern'"), Statement::ShowTables { + terse: false, + history: false, extended: false, full: false, - clause: None, - db_name: None, + external: false, + starts_with: None, + limit: None, + from: None, + show_in: None, filter: Some(ShowStatementFilter::Like("pattern".into())), + filter_position: ShowStatementFilterPosition::AtTheEnd } ); assert_eq!( mysql_and_generic().verified_stmt("SHOW TABLES WHERE 1 = 2"), Statement::ShowTables { + terse: false, + history: false, extended: false, full: false, - clause: None, - db_name: None, + external: false, + starts_with: None, + limit: None, + from: None, + show_in: None, filter: Some(ShowStatementFilter::Where( mysql_and_generic().verified_expr("1 = 2") )), + filter_position: ShowStatementFilterPosition::AtTheEnd } ); mysql_and_generic().verified_stmt("SHOW TABLES IN mydb"); diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index c17c7b958..5d70ac543 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -2781,3 +2781,68 @@ fn test_parentheses_overflow() { snowflake_with_recursion_limit(max_nesting_level).parse_sql_statements(sql.as_str()); assert_eq!(parsed.err(), Some(ParserError::RecursionLimitExceeded)); } + +#[test] +fn test_show_databases() { + snowflake().verified_stmt("SHOW DATABASES"); + snowflake().verified_stmt("SHOW DATABASES HISTORY"); + snowflake().verified_stmt("SHOW DATABASES LIKE '%abc%'"); + snowflake().verified_stmt("SHOW DATABASES STARTS WITH 'demo_db'"); + snowflake().verified_stmt("SHOW DATABASES LIMIT 12"); + snowflake() + .verified_stmt("SHOW DATABASES HISTORY LIKE '%aa' STARTS WITH 'demo' LIMIT 20 FROM 'abc'"); + snowflake().verified_stmt("SHOW DATABASES IN ACCOUNT abc"); +} + +#[test] +fn test_parse_show_schemas() { + snowflake().verified_stmt("SHOW SCHEMAS"); + snowflake().verified_stmt("SHOW SCHEMAS IN ACCOUNT"); + snowflake().verified_stmt("SHOW SCHEMAS IN ACCOUNT abc"); + snowflake().verified_stmt("SHOW SCHEMAS IN DATABASE"); + snowflake().verified_stmt("SHOW SCHEMAS IN DATABASE xyz"); + snowflake().verified_stmt("SHOW SCHEMAS HISTORY LIKE '%xa%'"); + snowflake().verified_stmt("SHOW SCHEMAS STARTS WITH 'abc' LIMIT 20"); + snowflake().verified_stmt("SHOW SCHEMAS IN DATABASE STARTS WITH 'abc' LIMIT 20 FROM 'xyz'"); +} + +#[test] +fn test_parse_show_tables() { + snowflake().verified_stmt("SHOW TABLES"); + snowflake().verified_stmt("SHOW TABLES IN ACCOUNT"); + snowflake().verified_stmt("SHOW TABLES IN DATABASE"); + snowflake().verified_stmt("SHOW TABLES IN DATABASE xyz"); + snowflake().verified_stmt("SHOW TABLES IN SCHEMA"); + snowflake().verified_stmt("SHOW TABLES IN SCHEMA xyz"); + snowflake().verified_stmt("SHOW TABLES HISTORY LIKE '%xa%'"); + snowflake().verified_stmt("SHOW TABLES STARTS WITH 'abc' LIMIT 20"); + snowflake().verified_stmt("SHOW TABLES IN SCHEMA STARTS WITH 'abc' LIMIT 20 FROM 'xyz'"); + snowflake().verified_stmt("SHOW EXTERNAL TABLES"); + snowflake().verified_stmt("SHOW EXTERNAL TABLES IN ACCOUNT"); + snowflake().verified_stmt("SHOW EXTERNAL TABLES IN DATABASE"); + snowflake().verified_stmt("SHOW EXTERNAL TABLES IN DATABASE xyz"); + snowflake().verified_stmt("SHOW EXTERNAL TABLES IN SCHEMA"); + snowflake().verified_stmt("SHOW EXTERNAL TABLES IN SCHEMA xyz"); + snowflake().verified_stmt("SHOW EXTERNAL TABLES STARTS WITH 'abc' LIMIT 20"); + snowflake() + .verified_stmt("SHOW EXTERNAL TABLES IN SCHEMA STARTS WITH 'abc' LIMIT 20 FROM 'xyz'"); +} + +#[test] +fn test_show_views() { + snowflake().verified_stmt("SHOW VIEWS"); + snowflake().verified_stmt("SHOW VIEWS IN ACCOUNT"); + snowflake().verified_stmt("SHOW VIEWS IN DATABASE"); + snowflake().verified_stmt("SHOW VIEWS IN DATABASE xyz"); + snowflake().verified_stmt("SHOW VIEWS IN SCHEMA"); + snowflake().verified_stmt("SHOW VIEWS IN SCHEMA xyz"); + snowflake().verified_stmt("SHOW VIEWS STARTS WITH 'abc' LIMIT 20"); + snowflake().verified_stmt("SHOW VIEWS IN SCHEMA STARTS WITH 'abc' LIMIT 20 FROM 'xyz'"); +} + +#[test] +fn test_parse_show_columns_sql() { + snowflake().verified_stmt("SHOW COLUMNS IN TABLE abc"); + snowflake().verified_stmt("SHOW COLUMNS LIKE '%xyz%' IN TABLE abc"); + snowflake().verified_stmt("SHOW COLUMNS IN TABLE"); +}