-
Notifications
You must be signed in to change notification settings - Fork 749
Add support for TABLE command in Postgresql #2364
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7cae107
e499266
db1fe42
faae354
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -176,7 +176,7 @@ pub enum SetExpr { | |||||||||||||||||||||
| /// `MERGE` statement | ||||||||||||||||||||||
| Merge(Statement), | ||||||||||||||||||||||
| /// `TABLE` command | ||||||||||||||||||||||
| Table(Box<Table>), | ||||||||||||||||||||||
| Table(Box<ExplicitTable>), | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| impl SetExpr { | ||||||||||||||||||||||
|
|
@@ -293,28 +293,49 @@ impl fmt::Display for SetQuantifier { | |||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// SQL:2016 `<explicit table>`: `TABLE <name>`, shorthand for `SELECT * FROM <name>`. | ||||||||||||||||||||||
| /// Postgres extends with `ONLY` and trailing `*`; see [`InheritanceModifier`]. | ||||||||||||||||||||||
| /// | ||||||||||||||||||||||
| /// <https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#explicit-table> | ||||||||||||||||||||||
| /// <https://www.postgresql.org/docs/current/sql-select.html#SQL-TABLE> | ||||||||||||||||||||||
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | ||||||||||||||||||||||
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||||||||||||||||||||||
| /// A [`TABLE` command]( https://www.postgresql.org/docs/current/sql-select.html#SQL-TABLE) | ||||||||||||||||||||||
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | ||||||||||||||||||||||
| /// A (possibly schema-qualified) table reference used in `FROM` clauses. | ||||||||||||||||||||||
| pub struct Table { | ||||||||||||||||||||||
| /// Optional table name (absent for e.g. `TABLE` command without argument). | ||||||||||||||||||||||
| pub table_name: Option<String>, | ||||||||||||||||||||||
| /// Optional schema/catalog name qualifying the table. | ||||||||||||||||||||||
| pub schema_name: Option<String>, | ||||||||||||||||||||||
| pub struct ExplicitTable { | ||||||||||||||||||||||
| /// The (possibly schema-qualified) table name. | ||||||||||||||||||||||
| pub name: ObjectName, | ||||||||||||||||||||||
| /// Postgres inheritance modifier (`ONLY` or trailing `*`), if present. | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
the rest of the comment is duplicated, the pg part we can drop since other dialects can support it either now or in the future |
||||||||||||||||||||||
| pub inheritance: InheritanceModifier, | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be an Option instead of introducing a custom None variant in the enum |
||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// Postgres inheritance-hierarchy modifier for table references. | ||||||||||||||||||||||
| /// | ||||||||||||||||||||||
| /// Controls whether a query against a table includes rows from descendant | ||||||||||||||||||||||
| /// tables (inheritance children or partitions). See | ||||||||||||||||||||||
| /// <https://www.postgresql.org/docs/current/ddl-inherit.html>. | ||||||||||||||||||||||
|
Comment on lines
+311
to
+315
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
| #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | ||||||||||||||||||||||
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||||||||||||||||||||||
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | ||||||||||||||||||||||
| pub enum InheritanceModifier { | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
| /// No modifier — default behavior (descendants included). | ||||||||||||||||||||||
| None, | ||||||||||||||||||||||
| /// `ONLY` prefix — exclude rows from descendant tables. | ||||||||||||||||||||||
| Only, | ||||||||||||||||||||||
| /// Trailing `*` — explicitly include descendant rows. Same effect as | ||||||||||||||||||||||
| /// `None`, preserved as a distinct variant so round-tripping echoes | ||||||||||||||||||||||
| /// what the user wrote. | ||||||||||||||||||||||
| IncludeDescendants, | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| impl fmt::Display for Table { | ||||||||||||||||||||||
| impl fmt::Display for ExplicitTable { | ||||||||||||||||||||||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||||||||||||||||||
| if let Some(ref table_name) = self.table_name { | ||||||||||||||||||||||
| if let Some(ref schema_name) = self.schema_name { | ||||||||||||||||||||||
| write!(f, "TABLE {}.{}", schema_name, table_name,)?; | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| write!(f, "TABLE {}", table_name)?; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| write!(f, "TABLE")?; | ||||||||||||||||||||||
| f.write_str("TABLE ")?; | ||||||||||||||||||||||
| if self.inheritance == InheritanceModifier::Only { | ||||||||||||||||||||||
| f.write_str("ONLY ")?; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| write!(f, "{}", self.name)?; | ||||||||||||||||||||||
| if self.inheritance == InheritanceModifier::IncludeDescendants { | ||||||||||||||||||||||
| f.write_str(" *")?; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1425,6 +1425,29 @@ pub trait Dialect: Debug + Any { | |
| fn supports_array_typedef_with_brackets(&self) -> bool { | ||
| false | ||
| } | ||
|
|
||
| /// Returns true if the dialect supports the `TABLE` command | ||
| /// (SQL:2016 `<explicit table>`). See [`ExplicitTable`]. | ||
| fn supports_table_command(&self) -> bool { | ||
| false | ||
| } | ||
|
|
||
| /// Returns true if the dialect supports Postgres inheritance modifiers | ||
| /// (`ONLY` prefix and trailing `*`) on the `TABLE` command. | ||
| /// See [`InheritanceModifier`]. | ||
| fn supports_explicit_table_inheritance_modifiers(&self) -> bool { | ||
| false | ||
| } | ||
|
|
||
| /// Returns the maximum number of dot-separated parts allowed in a | ||
| /// table name for the `TABLE` command. For example, `2` means only | ||
| /// `schema.table` is accepted; `3` would allow `catalog.schema.table`. | ||
| /// | ||
| /// Returns `None` if the dialect does not restrict the number of parts. | ||
| fn table_command_max_name_parts(&self) -> Option<usize> { | ||
| None | ||
|
Comment on lines
+1429
to
+1448
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should be able to drop these dialect methods, the latter two are a bit too specific I think, and in general since we're introducing an entirely new statement it should be fine to have the parser always accept the statement without it conflicting with other dialects |
||
| } | ||
|
|
||
| /// Returns true if the dialect supports geometric types. | ||
| /// | ||
| /// Postgres: <https://www.postgresql.org/docs/9.5/functions-geometry.html> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -565,7 +565,7 @@ impl<'a> Parser<'a> { | |
| } | ||
|
|
||
| /// Convenience method to parse a string with one or more SQL | ||
| /// statements into produce an Abstract Syntax Tree (AST). | ||
| /// statements to produce an Abstract Syntax Tree (AST). | ||
| /// | ||
| /// Example | ||
| /// ``` | ||
|
|
@@ -660,6 +660,10 @@ impl<'a> Parser<'a> { | |
| self.prev_token(); | ||
| self.parse_query().map(Into::into) | ||
| } | ||
| Keyword::TABLE if self.dialect.supports_table_command() => { | ||
| self.prev_token(); | ||
| self.parse_query().map(Into::into) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. which statement type is being returned? |
||
| } | ||
| Keyword::TRUNCATE => self.parse_truncate().map(Into::into), | ||
| Keyword::ATTACH => { | ||
| if dialect_of!(self is DuckDbDialect) { | ||
|
|
@@ -14975,8 +14979,8 @@ impl<'a> Parser<'a> { | |
| } 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()?)) | ||
| } else if self.dialect.supports_table_command() && self.parse_keyword(Keyword::TABLE) { | ||
| SetExpr::Table(Box::new(self.parse_explicit_table()?)) | ||
| } else { | ||
| return self.expected_ref( | ||
| "SELECT, VALUES, or a subquery in the query body", | ||
|
|
@@ -15480,49 +15484,40 @@ impl<'a> Parser<'a> { | |
| Ok(clauses) | ||
| } | ||
|
|
||
| /// Parse `CREATE TABLE x AS TABLE y` | ||
| pub fn parse_as_table(&mut self) -> Result<Table, ParserError> { | ||
| let token1 = self.next_token(); | ||
| let token2 = self.next_token(); | ||
| let token3 = self.next_token(); | ||
| /// Parse the body of a TABLE query expression. | ||
| /// Called after the `TABLE` keyword has been consumed. | ||
| pub fn parse_explicit_table(&mut self) -> Result<ExplicitTable, ParserError> { | ||
| let allow_inheritance = self.dialect.supports_explicit_table_inheritance_modifiers(); | ||
|
|
||
| let table_name; | ||
| let schema_name; | ||
| if token2 == Token::Period { | ||
| match token1.token { | ||
| Token::Word(w) => { | ||
| schema_name = w.value; | ||
| } | ||
| _ => { | ||
| return self.expected("Schema name", token1); | ||
| } | ||
| } | ||
| match token3.token { | ||
| Token::Word(w) => { | ||
| table_name = w.value; | ||
| } | ||
| _ => { | ||
| return self.expected("Table name", token3); | ||
| } | ||
| } | ||
| Ok(Table { | ||
| table_name: Some(table_name), | ||
| schema_name: Some(schema_name), | ||
| }) | ||
| } else { | ||
| match token1.token { | ||
| Token::Word(w) => { | ||
| table_name = w.value; | ||
| } | ||
| _ => { | ||
| return self.expected("Table name", token1); | ||
| } | ||
| let has_only = allow_inheritance && self.parse_keyword(Keyword::ONLY); | ||
| let parenthesized = has_only && self.consume_token(&Token::LParen); | ||
|
|
||
| let name = self.parse_object_name(true)?; | ||
|
|
||
| if let Some(max) = self.dialect.table_command_max_name_parts() { | ||
| if name.0.len() > max { | ||
| return self.expected_ref( | ||
| "a table name (optionally schema-qualified)", | ||
| self.peek_token_ref(), | ||
| ); | ||
| } | ||
| Ok(Table { | ||
| table_name: Some(table_name), | ||
| schema_name: None, | ||
| }) | ||
| } | ||
|
|
||
| if parenthesized { | ||
| self.expect_token(&Token::RParen)?; | ||
| } | ||
|
|
||
| let has_star = allow_inheritance && !has_only && self.consume_token(&Token::Mul); | ||
|
|
||
| let inheritance = if has_only { | ||
| InheritanceModifier::Only | ||
| } else if has_star { | ||
| InheritanceModifier::IncludeDescendants | ||
| } else { | ||
| InheritanceModifier::None | ||
| }; | ||
|
|
||
| Ok(ExplicitTable { name, inheritance }) | ||
| } | ||
|
|
||
| /// Parse a `SET ROLE` statement. Expects SET to be consumed already. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm not sure if there's a need to rename Table (its not clear to me what we gain by doing so)?