diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 41ee3fd3f6..7dc14d6cce 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -5184,12 +5184,12 @@ impl fmt::Display for Statement { } => { write!(f, "{describe_alias} ")?; - if let Some(format) = hive_format { - write!(f, "{format} ")?; - } if *has_table_keyword { write!(f, "TABLE ")?; } + if let Some(format) = hive_format { + write!(f, "{format} ")?; + } write!(f, "{table_name}") } diff --git a/src/dialect/databricks.rs b/src/dialect/databricks.rs index dd8c2fcfed..e649e02572 100644 --- a/src/dialect/databricks.rs +++ b/src/dialect/databricks.rs @@ -54,6 +54,16 @@ impl Dialect for DatabricksDialect { true } + /// `TABLE t` as a statement + fn supports_table_statement(&self) -> bool { + true + } + + /// `VALUES (1, 2) AS t(a, b)` as a query body + fn supports_values_alias(&self) -> bool { + true + } + /// `1Y`, `1S`, `1L`, `1.0F`, `1.0D`, `1.0BD` fn supports_typed_numeric_literal_suffix(&self) -> bool { true diff --git a/src/dialect/hive.rs b/src/dialect/hive.rs index ed8ed63496..3a4207bec5 100644 --- a/src/dialect/hive.rs +++ b/src/dialect/hive.rs @@ -52,6 +52,16 @@ impl Dialect for HiveDialect { true } + /// `TABLE t` as a statement + fn supports_table_statement(&self) -> bool { + true + } + + /// `VALUES (1, 2) AS t(a, b)` as a query body + fn supports_values_alias(&self) -> bool { + true + } + /// `1Y`, `1S`, `1L`, `1.0F`, `1.0D`, `1.0BD` fn supports_typed_numeric_literal_suffix(&self) -> bool { true diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 763830d138..801c5351bd 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -508,6 +508,19 @@ pub trait Dialect: Debug + Any { false } + /// Returns true if a statement may be a bare `TABLE t`, meaning + /// `SELECT * FROM t`, as in Spark SQL. + fn supports_table_statement(&self) -> bool { + false + } + + /// Returns true if a `VALUES` query body may carry a table alias with a + /// column list, `VALUES (1, 2) AS t(a, b)`, as in Spark SQL. It parses to + /// `SELECT * FROM (VALUES (1, 2)) AS t(a, b)`. + fn supports_values_alias(&self) -> bool { + false + } + /// Returns true if the dialect supports numbers containing underscores, e.g. `10_000_000` fn supports_numeric_literal_underscores(&self) -> bool { false diff --git a/src/dialect/spark.rs b/src/dialect/spark.rs index b4941b7f69..1faef11be2 100644 --- a/src/dialect/spark.rs +++ b/src/dialect/spark.rs @@ -55,6 +55,16 @@ impl Dialect for SparkSqlDialect { true } + /// `TABLE t` as a statement + fn supports_table_statement(&self) -> bool { + true + } + + /// `VALUES (1, 2) AS t(a, b)` as a query body + fn supports_values_alias(&self) -> bool { + true + } + /// `1Y`, `1S`, `1L`, `1.0F`, `1.0D`, `1.0BD` fn supports_typed_numeric_literal_suffix(&self) -> bool { true diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 3520b7050c..4f6c77077c 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -650,6 +650,10 @@ impl<'a> Parser<'a> { self.prev_token(); self.parse_query().map(Into::into) } + Keyword::TABLE if self.dialect.supports_table_statement() => { + self.prev_token(); + self.parse_query().map(Into::into) + } Keyword::TRUNCATE => self.parse_truncate().map(Into::into), Keyword::ATTACH => { if dialect_of!(self is DuckDbDialect) { @@ -14585,7 +14589,22 @@ impl<'a> Parser<'a> { } } - match self.maybe_parse(|parser| parser.parse_statement())? { + // Spark's `DESC [TABLE] [EXTENDED | FORMATTED] name`: where `TABLE` can + // start a query, `DESC TABLE t` would otherwise describe the query `TABLE t`. + // `DESCRIBE table` and `DESCRIBE table.x` still name a table called `table`. + let describe_table = describe_alias != DescribeAlias::Explain + && self.dialect.supports_table_statement() + && self.peek_keyword(Keyword::TABLE) + && !matches!( + self.peek_nth_token_ref(1).token, + Token::EOF | Token::SemiColon | Token::Period + ); + let statement = if describe_table { + None + } else { + self.maybe_parse(|parser| parser.parse_statement())? + }; + match statement { Some(Statement::Explain { .. }) | Some(Statement::ExplainTable { .. }) => Err( ParserError::ParserError("Explain must be root of the plan".to_string()), ), @@ -14600,19 +14619,18 @@ impl<'a> Parser<'a> { options, }), _ => { - let hive_format = - match self.parse_one_of_keywords(&[Keyword::EXTENDED, Keyword::FORMATTED]) { - Some(Keyword::EXTENDED) => Some(HiveDescribeFormat::Extended), - Some(Keyword::FORMATTED) => Some(HiveDescribeFormat::Formatted), - _ => None, - }; + let mut hive_format = self.parse_hive_describe_format(); - let has_table_keyword = if self.dialect.describe_requires_table_keyword() { - // only allow to use TABLE keyword for DESC|DESCRIBE statement - self.parse_keyword(Keyword::TABLE) - } else { - false - }; + let has_table_keyword = + if self.dialect.describe_requires_table_keyword() || describe_table { + // only allow to use TABLE keyword for DESC|DESCRIBE statement + self.parse_keyword(Keyword::TABLE) + } else { + false + }; + if has_table_keyword && hive_format.is_none() { + hive_format = self.parse_hive_describe_format(); + } let table_name = self.parse_object_name(false)?; Ok(Statement::ExplainTable { @@ -14625,6 +14643,14 @@ impl<'a> Parser<'a> { } } + fn parse_hive_describe_format(&mut self) -> Option { + match self.parse_one_of_keywords(&[Keyword::EXTENDED, Keyword::FORMATTED]) { + Some(Keyword::EXTENDED) => Some(HiveDescribeFormat::Extended), + Some(Keyword::FORMATTED) => Some(HiveDescribeFormat::Formatted), + _ => None, + } + } + /// Parse a query expression, i.e. a `SELECT` statement optionally /// preceded with some `WITH` CTE declarations and optionally followed /// by `ORDER BY`. Unlike some other parse_... methods, this one doesn't @@ -15209,12 +15235,40 @@ impl<'a> Parser<'a> { SetExpr::Query(subquery) } else if self.parse_keyword(Keyword::VALUES) { let is_mysql = dialect_of!(self is MySqlDialect); - SetExpr::Values(self.parse_values(is_mysql, false)?) + let values = SetExpr::Values(self.parse_values(is_mysql, false)?); + // The alias belongs to this operand, so read it before any set operator. + let alias = if self.dialect.supports_values_alias() { + self.maybe_parse_table_alias()? + } else { + None + }; + match alias { + Some(alias) => Self::values_with_alias(Box::new(values), alias), + None => values, + } } else if self.parse_keyword(Keyword::VALUE) { let is_mysql = dialect_of!(self is MySqlDialect); SetExpr::Values(self.parse_values(is_mysql, true)?) } else if self.parse_keyword(Keyword::TABLE) { - SetExpr::Table(Box::new(self.parse_as_table()?)) + if self.dialect.supports_table_statement() { + // `TABLE t` is `SELECT * FROM t`; keeping the ObjectName + // preserves quoting, which the string-only `Table` drops. + let name = self.parse_object_name(false)?; + Self::select_star_from(TableFactor::Table { + name, + alias: None, + args: None, + with_hints: vec![], + version: None, + partitions: vec![], + with_ordinality: false, + json_path: None, + sample: None, + index_hints: vec![], + }) + } else { + SetExpr::Table(Box::new(self.parse_as_table()?)) + } } else { return self.expected_ref( "SELECT, VALUES, or a subquery in the query body", @@ -15796,11 +15850,77 @@ impl<'a> Parser<'a> { Ok(clauses) } + /// `VALUES ... AS t(a, b)` as `SELECT * FROM (VALUES ...) AS t(a, b)`: + /// the alias has nowhere to go on a bare `SetExpr::Values`. + fn values_with_alias(values: Box, alias: TableAlias) -> SetExpr { + let subquery = Box::new(Query { + with: None, + body: values, + order_by: None, + limit_clause: None, + fetch: None, + locks: vec![], + for_clause: None, + settings: None, + format_clause: None, + pipe_operators: vec![], + }); + Self::select_star_from(TableFactor::Derived { + lateral: false, + subquery, + alias: Some(alias), + sample: None, + }) + } + + fn select_star_from(relation: TableFactor) -> SetExpr { + SetExpr::Select(Box::new(Select { + select_token: AttachedToken::empty(), + optimizer_hints: vec![], + distinct: None, + select_modifiers: None, + top: None, + top_before_distinct: false, + projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions::default())], + exclude: None, + into: None, + from: vec![TableWithJoins { + relation, + joins: vec![], + }], + lateral_views: vec![], + prewhere: None, + selection: None, + group_by: GroupByExpr::Expressions(vec![], vec![]), + cluster_by: vec![], + distribute_by: vec![], + sort_by: vec![], + having: None, + named_window: vec![], + window_before_qualify: false, + qualify: None, + value_table_mode: None, + connect_by: vec![], + flavor: SelectFlavor::Standard, + })) + } + /// Parse `CREATE TABLE x AS TABLE y` pub fn parse_as_table(&mut self) -> Result { let token1 = self.next_token(); - let token2 = self.next_token(); - let token3 = self.next_token(); + // Only a `.` continues the name; anything else (ORDER BY, a pipe + // operator, the end of the statement) belongs to the caller. + let qualified = self.peek_token_ref().token == Token::Period; + let token2 = if qualified { + self.next_token() + } else { + TokenWithSpan::wrap(Token::EOF) + }; + let token3 = if qualified { + self.next_token() + } else { + TokenWithSpan::wrap(Token::EOF) + }; let table_name; let schema_name; diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index d33b1e6cdf..ef800256d3 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -4843,6 +4843,8 @@ fn parse_create_table_as() { #[test] fn parse_create_table_as_table() { + // Dialects with `supports_table_statement` read `TABLE t` as `SELECT * FROM t`. + let dialects = all_dialects_where(|d| !d.supports_table_statement()); let sql1 = "CREATE TABLE new_table AS TABLE old_table"; let expected_query1 = Box::new(Query { @@ -4861,7 +4863,7 @@ fn parse_create_table_as_table() { pipe_operators: vec![], }); - match verified_stmt(sql1) { + match dialects.verified_stmt(sql1) { Statement::CreateTable(CreateTable { query, name, .. }) => { assert_eq!(name, ObjectName::from(vec![Ident::new("new_table")])); assert_eq!(query.unwrap(), expected_query1); @@ -4887,7 +4889,7 @@ fn parse_create_table_as_table() { pipe_operators: vec![], }); - match verified_stmt(sql2) { + match dialects.verified_stmt(sql2) { Statement::CreateTable(CreateTable { query, name, .. }) => { assert_eq!(name, ObjectName::from(vec![Ident::new("new_table")])); assert_eq!(query.unwrap(), expected_query2); diff --git a/tests/sqlparser_spark.rs b/tests/sqlparser_spark.rs index 0f292f0e98..a29be44cd7 100644 --- a/tests/sqlparser_spark.rs +++ b/tests/sqlparser_spark.rs @@ -362,3 +362,126 @@ fn test_substring() { fn test_pipe_operator() { spark().verified_stmt("SELECT * FROM t |> WHERE x > 1 |> SELECT x AS y |> ORDER BY y"); } + +// -------------------------------- +// TABLE t, VALUES ... AS t(a, b) +// -------------------------------- + +#[test] +fn test_table_statement() { + // `TABLE t` is read as `SELECT * FROM t`, wherever a query body can go. + spark().one_statement_parses_to("TABLE t", "SELECT * FROM t"); + spark().one_statement_parses_to("TABLE db.t", "SELECT * FROM db.t"); + spark().one_statement_parses_to("TABLE cat.db.t", "SELECT * FROM cat.db.t"); + spark().one_statement_parses_to("TABLE `My T`", "SELECT * FROM `My T`"); + spark().one_statement_parses_to( + "TABLE t ORDER BY a DESC LIMIT 1", + "SELECT * FROM t ORDER BY a DESC LIMIT 1", + ); + spark().one_statement_parses_to( + "TABLE t1 UNION ALL TABLE t2", + "SELECT * FROM t1 UNION ALL SELECT * FROM t2", + ); + spark().one_statement_parses_to( + "SELECT a FROM t1 EXCEPT TABLE t2", + "SELECT a FROM t1 EXCEPT SELECT * FROM t2", + ); + spark().one_statement_parses_to( + "WITH c AS (TABLE t) TABLE c", + "WITH c AS (SELECT * FROM t) SELECT * FROM c", + ); + spark().one_statement_parses_to( + "CREATE TABLE t2 AS TABLE db.t1", + "CREATE TABLE t2 AS SELECT * FROM db.t1", + ); + spark().one_statement_parses_to("INSERT INTO t2 TABLE t1", "INSERT INTO t2 SELECT * FROM t1"); + let stmt = spark().one_statement_parses_to("TABLE `My T`", "SELECT * FROM `My T`"); + let Statement::Query(q) = stmt else { + panic!("Expected Query") + }; + let SetExpr::Select(select) = *q.body else { + panic!("Expected a SELECT body") + }; + let TableFactor::Table { name, .. } = &select.from[0].relation else { + panic!("Expected a table") + }; + assert_eq!(name.0[0].as_ident().unwrap().quote_style, Some('`')); +} + +#[test] +fn test_values_with_alias() { + spark().one_statement_parses_to( + "VALUES (1, 2), (3, 4) AS t(a, b)", + "SELECT * FROM (VALUES (1, 2), (3, 4)) AS t (a, b)", + ); + spark().one_statement_parses_to( + "CREATE TEMPORARY VIEW v AS VALUES (1, 2) AS t(a, b)", + "CREATE TEMPORARY VIEW v AS SELECT * FROM (VALUES (1, 2)) AS t (a, b)", + ); + // The alias belongs to its own side of a set operation. + spark().one_statement_parses_to( + "VALUES (1, 2) AS t(a, b) UNION ALL VALUES (3, 4) AS u(a, b)", + "SELECT * FROM (VALUES (1, 2)) AS t (a, b) UNION ALL SELECT * FROM (VALUES (3, 4)) AS u (a, b)", + ); + spark().one_statement_parses_to( + "SELECT 1 EXCEPT VALUES (1) AS t(a)", + "SELECT 1 EXCEPT SELECT * FROM (VALUES (1)) AS t (a)", + ); + spark().one_statement_parses_to( + "VALUES (1) AS t(a) INTERSECT TABLE u", + "SELECT * FROM (VALUES (1)) AS t (a) INTERSECT SELECT * FROM u", + ); + spark().one_statement_parses_to( + "VALUES (1) AS t(a) ORDER BY a LIMIT 1", + "SELECT * FROM (VALUES (1)) AS t (a) ORDER BY a LIMIT 1", + ); + spark().one_statement_parses_to( + "WITH c AS (VALUES (1) AS t(a)) SELECT a FROM c", + "WITH c AS (SELECT * FROM (VALUES (1)) AS t (a)) SELECT a FROM c", + ); + // FROM VALUES keeps its own table-factor form. + spark().one_statement_parses_to( + "SELECT * FROM VALUES (1, 2) AS t (a, b)", + "SELECT * FROM (VALUES (1, 2)) AS t (a, b)", + ); + // Without an alias the body stays a plain VALUES, and a set operation + // after VALUES is not read as an alias. + spark().verified_stmt("VALUES (1, 2)"); + spark().verified_stmt("VALUES (1) UNION ALL VALUES (2)"); +} + +#[test] +fn test_describe_table_is_not_a_table_query() { + // `TABLE` after DESC/DESCRIBE names the table being described; it does + // not start a `TABLE t` query. + for sql in [ + "DESCRIBE TABLE t", + "DESC TABLE db.t", + "DESCRIBE TABLE EXTENDED t", + "DESC TABLE FORMATTED db.t", + ] { + match spark().verified_stmt(sql) { + Statement::ExplainTable { + has_table_keyword, .. + } => assert!(has_table_keyword, "{sql}"), + other => panic!("Expected ExplainTable for {sql}, got {other:?}"), + } + } + match spark().verified_stmt("DESCRIBE TABLE EXTENDED db.t") { + Statement::ExplainTable { + hive_format, + table_name, + .. + } => { + assert_eq!(hive_format, Some(HiveDescribeFormat::Extended)); + assert_eq!(table_name.to_string(), "db.t"); + } + _ => unreachable!(), + } + spark().verified_stmt("DESCRIBE EXTENDED t"); + // A table called `table` is still described by name. + spark().verified_stmt("DESCRIBE table"); + spark().verified_stmt("DESCRIBE table.t"); + // EXPLAIN is unaffected. + spark().one_statement_parses_to("EXPLAIN TABLE t", "EXPLAIN SELECT * FROM t"); +}