Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,24 +92,24 @@ pub use self::dml::{
pub use self::operator::{BinaryOperator, UnaryOperator};
pub use self::query::{
AfterMatchSkip, ConnectByKind, Cte, CteAsMaterialized, Distinct, EmptyMatchesMode,
ExceptSelectItem, ExcludeSelectItem, ExprWithAlias, ExprWithAliasAndOrderBy, Fetch, ForClause,
ForJson, ForXml, FormatClause, GroupByExpr, GroupByWithModifier, IdentWithAlias,
IlikeSelectItem, InputFormatClause, Interpolate, InterpolateExpr, Join, JoinConstraint,
JoinOperator, JsonTableColumn, JsonTableColumnErrorHandling, JsonTableNamedColumn,
JsonTableNestedColumn, LateralView, LimitClause, LockClause, LockType, MatchRecognizePattern,
MatchRecognizeSymbol, Measure, NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset,
OffsetRows, OpenJsonTableColumn, OrderBy, OrderByExpr, OrderByKind, OrderByOptions,
OrderBySort, PipeOperator, PivotValueSource, ProjectionSelect, Query, RenameSelectItem,
RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select,
SelectFlavor, SelectInto, SelectItem, SelectItemQualifiedWildcardKind, SelectModifiers,
SetExpr, SetOperator, SetQuantifier, Setting, SymbolDefinition, Table, TableAlias,
TableAliasColumnDef, TableFactor, TableFunctionArgs, TableIndexHintForClause,
TableIndexHintType, TableIndexHints, TableIndexType, TableSample, TableSampleBucket,
TableSampleKind, TableSampleMethod, TableSampleModifier, TableSampleQuantity, TableSampleSeed,
TableSampleSeedModifier, TableSampleUnit, TableVersion, TableWithJoins, Top, TopQuantity,
UpdateTableFromKind, ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill,
XmlNamespaceDefinition, XmlPassingArgument, XmlPassingClause, XmlTableColumn,
XmlTableColumnOption,
ExceptSelectItem, ExcludeSelectItem, ExplicitTable, ExprWithAlias, ExprWithAliasAndOrderBy,
Fetch, ForClause, ForJson, ForXml, FormatClause, GroupByExpr, GroupByWithModifier,
IdentWithAlias, IlikeSelectItem, InheritanceModifier, InputFormatClause, Interpolate,
InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonTableColumn,
JsonTableColumnErrorHandling, JsonTableNamedColumn, JsonTableNestedColumn, LateralView,
LimitClause, LockClause, LockType, MatchRecognizePattern, MatchRecognizeSymbol, Measure,
NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows, OpenJsonTableColumn,
OrderBy, OrderByExpr, OrderByKind, OrderByOptions, OrderBySort, PipeOperator, PivotValueSource,
ProjectionSelect, Query, RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement,
ReplaceSelectItem, RowsPerMatch, Select, SelectFlavor, SelectInto, SelectItem,
SelectItemQualifiedWildcardKind, SelectModifiers, SetExpr, SetOperator, SetQuantifier, Setting,
SymbolDefinition, TableAlias, TableAliasColumnDef, TableFactor, TableFunctionArgs,
TableIndexHintForClause, TableIndexHintType, TableIndexHints, TableIndexType, TableSample,
TableSampleBucket, TableSampleKind, TableSampleMethod, TableSampleModifier,
TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier, TableSampleUnit, TableVersion,
TableWithJoins, Top, TopQuantity, UpdateTableFromKind, ValueTableMode, Values,
WildcardAdditionalOptions, With, WithFill, XmlNamespaceDefinition, XmlPassingArgument,
XmlPassingClause, XmlTableColumn, XmlTableColumnOption,
};

pub use self::trigger::{
Expand Down
55 changes: 38 additions & 17 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ pub enum SetExpr {
/// `MERGE` statement
Merge(Statement),
/// `TABLE` command
Table(Box<Table>),
Table(Box<ExplicitTable>),
}

impl SetExpr {
Expand Down Expand Up @@ -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.
pub inheritance: InheritanceModifier,
}

/// 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>.
#[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 {
/// 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(())
}
Expand Down
5 changes: 5 additions & 0 deletions src/dialect/ansi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,9 @@ impl Dialect for AnsiDialect {
fn supports_nested_comments(&self) -> bool {
true
}

/// SQL:2016 `<explicit table>`.
fn supports_table_command(&self) -> bool {
true
}
}
8 changes: 8 additions & 0 deletions src/dialect/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,4 +312,12 @@ impl Dialect for GenericDialect {
fn supports_xml_expressions(&self) -> bool {
true
}

fn supports_table_command(&self) -> bool {
true
}

fn supports_explicit_table_inheritance_modifiers(&self) -> bool {
true
}
}
14 changes: 14 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1398,6 +1398,20 @@ 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 true if the dialect supports geometric types.
///
/// Postgres: <https://www.postgresql.org/docs/9.5/functions-geometry.html>
Expand Down
8 changes: 8 additions & 0 deletions src/dialect/postgresql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,4 +330,12 @@ impl Dialect for PostgreSqlDialect {
fn supports_comment_optimizer_hint(&self) -> bool {
true
}

fn supports_table_command(&self) -> bool {
true
}

fn supports_explicit_table_inheritance_modifiers(&self) -> bool {
true
}
}
74 changes: 30 additions & 44 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,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
/// ```
Expand Down Expand Up @@ -623,6 +623,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)
}
Keyword::TRUNCATE => self.parse_truncate().map(Into::into),
Keyword::ATTACH => {
if dialect_of!(self is DuckDbDialect) {
Expand Down Expand Up @@ -14672,8 +14676,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",
Expand Down Expand Up @@ -15177,49 +15181,31 @@ 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);
}
}
Ok(Table {
table_name: Some(table_name),
schema_name: None,
})
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 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.
Expand Down
54 changes: 46 additions & 8 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4590,13 +4590,15 @@ fn parse_create_table_as() {

#[test]
fn parse_create_table_as_table() {
let dialects = all_dialects_where(|d| d.supports_table_command());

let sql1 = "CREATE TABLE new_table AS TABLE old_table";

let expected_query1 = Box::new(Query {
with: None,
body: Box::new(SetExpr::Table(Box::new(Table {
table_name: Some("old_table".to_string()),
schema_name: None,
body: Box::new(SetExpr::Table(Box::new(ExplicitTable {
name: ObjectName::from(vec![Ident::new("old_table")]),
inheritance: InheritanceModifier::None,
}))),
order_by: None,
limit_clause: None,
Expand All @@ -4608,7 +4610,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);
Expand All @@ -4620,9 +4622,9 @@ fn parse_create_table_as_table() {

let expected_query2 = Box::new(Query {
with: None,
body: Box::new(SetExpr::Table(Box::new(Table {
table_name: Some("old_table".to_string()),
schema_name: Some("schema_name".to_string()),
body: Box::new(SetExpr::Table(Box::new(ExplicitTable {
name: ObjectName::from(vec![Ident::new("schema_name"), Ident::new("old_table")]),
inheritance: InheritanceModifier::None,
}))),
order_by: None,
limit_clause: None,
Expand All @@ -4634,7 +4636,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);
Expand All @@ -4643,6 +4645,42 @@ fn parse_create_table_as_table() {
}
}

#[test]
fn parse_table_command() {
let dialects = all_dialects_where(|d| d.supports_table_command());

// Top-level.
dialects.verified_stmt("TABLE films");
// Schema-qualified.
dialects.verified_stmt("TABLE myschema.films");
// Database-qualified (three-part name).
dialects.verified_stmt("TABLE mydb.myschema.films");
// Quoted identifiers — round-trip preserves quoting and case.
dialects.verified_stmt("TABLE \"MyTable\"");
dialects.verified_stmt("TABLE \"My Schema\".\"My Table\"");
// ORDER BY + LIMIT + OFFSET.
dialects.verified_stmt("TABLE films ORDER BY did LIMIT 10 OFFSET 2");
// FETCH.
dialects.verified_stmt("TABLE films FETCH FIRST 3 ROWS ONLY");
// UNION of TABLE commands.
dialects.verified_stmt("TABLE a UNION TABLE b");
// INTERSECT, EXCEPT.
dialects.verified_stmt("TABLE a INTERSECT TABLE b");
dialects.verified_stmt("TABLE a EXCEPT TABLE b");
// Mixed with SELECT.
dialects.verified_stmt("TABLE a UNION ALL SELECT * FROM b");
// CTE body is TABLE.
dialects.verified_stmt("WITH x AS (TABLE films) SELECT * FROM x");
// WITH prefix + TABLE at top level.
dialects.verified_stmt("WITH x AS (SELECT 1) TABLE films");
// Derived table.
dialects.verified_stmt("SELECT * FROM (TABLE films) AS x");
// FOR locking clauses.
dialects.verified_stmt("TABLE films FOR UPDATE");
dialects.verified_stmt("TABLE films FOR SHARE");
dialects.verified_stmt("TABLE films FOR UPDATE OF films");
}

#[test]
fn parse_create_table_on_cluster() {
let generic = TestedDialects::new(vec![Box::new(GenericDialect {})]);
Expand Down
Loading