diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 656a90d2f..b5aa019ab 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -106,8 +106,8 @@ pub use self::query::{ 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, + SetExpr, SetOperator, SetQuantifier, Setting, SummarizeTarget, SymbolDefinition, Table, + TableAlias, TableAliasColumnDef, TableFactor, TableFunctionArgs, TableIndexHintForClause, TableIndexHintType, TableIndexHints, TableIndexType, TableSample, TableSampleBucket, TableSampleKind, TableSampleMethod, TableSampleModifier, TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier, TableSampleUnit, TableVersion, TableWithJoins, Top, TopQuantity, diff --git a/src/ast/query.rs b/src/ast/query.rs index 2ada46a9f..7ad485762 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -177,6 +177,32 @@ pub enum SetExpr { Merge(Statement), /// `TABLE` command Table(Box), + /// DuckDB `SUMMARIZE` query. + Summarize(SummarizeTarget), +} + +/// The relation or query summarized by DuckDB's `SUMMARIZE` syntax. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum SummarizeTarget { + /// A named table. + Table { + /// Table name. + #[cfg_attr(feature = "visitor", visit(with = "visit_relation"))] + name: ObjectName, + }, + /// A query whose output should be summarized. + Query(Box), +} + +impl fmt::Display for SummarizeTarget { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + SummarizeTarget::Table { name } => name.fmt(f), + SummarizeTarget::Query(query) => query.fmt(f), + } + } } impl SetExpr { @@ -205,6 +231,7 @@ impl fmt::Display for SetExpr { SetExpr::Delete(v) => v.fmt(f), SetExpr::Merge(v) => v.fmt(f), SetExpr::Table(t) => t.fmt(f), + SetExpr::Summarize(target) => write!(f, "SUMMARIZE {target}"), SetExpr::SetOperation { left, right, diff --git a/src/ast/spans.rs b/src/ast/spans.rs index a34fe66d9..473d779fd 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -45,9 +45,9 @@ use super::{ PartitionBoundValue, PivotValueSource, ProjectionSelect, Query, RaiseStatement, RaiseStatementValue, ReferentialAction, RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SqlOption, Statement, Subscript, - SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint, TableFactor, TableObject, - TableOptionsClustered, TableWithJoins, Update, UpdateTableFromKind, Use, Values, ViewColumnDef, - WhileStatement, WildcardAdditionalOptions, With, WithFill, + SummarizeTarget, SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint, + TableFactor, TableObject, TableOptionsClustered, TableWithJoins, Update, UpdateTableFromKind, + Use, Values, ViewColumnDef, WhileStatement, WildcardAdditionalOptions, With, WithFill, }; /// Given an iterator of spans, return the [Span::union] of all spans. @@ -233,6 +233,8 @@ impl Spanned for SetExpr { SetExpr::Update(statement) => statement.span(), SetExpr::Delete(statement) => statement.span(), SetExpr::Merge(statement) => statement.span(), + SetExpr::Summarize(SummarizeTarget::Table { name }) => name.span(), + SetExpr::Summarize(SummarizeTarget::Query(query)) => query.span(), } } } diff --git a/src/dialect/duckdb.rs b/src/dialect/duckdb.rs index 2e3673bc4..42d08b0f4 100644 --- a/src/dialect/duckdb.rs +++ b/src/dialect/duckdb.rs @@ -105,6 +105,10 @@ impl Dialect for DuckDbDialect { true } + fn supports_summarize(&self) -> bool { + true + } + /// See DuckDB fn supports_order_by_all(&self) -> bool { true diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index f99cbe2ea..588ffc9d9 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -697,6 +697,11 @@ pub trait Dialect: Debug + Any { false } + /// Return true if the dialect supports DuckDB-style `SUMMARIZE` queries. + fn supports_summarize(&self) -> bool { + false + } + /// Return true if the dialect supports "FROM-first" inserts. /// /// Example: diff --git a/src/keywords.rs b/src/keywords.rs index 0c50703c3..9a0479fab 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -1023,6 +1023,7 @@ define_keywords!( SUBTYPE_OPCLASS, SUCCEEDS, SUM, + SUMMARIZE, SUPER, SUPERUSER, SUPPORT, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 272de79d8..6dd8689f1 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -659,6 +659,10 @@ impl<'a> Parser<'a> { self.prev_token(); self.parse_query().map(Into::into) } + Keyword::SUMMARIZE if self.dialect.supports_summarize() => { + 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) { @@ -15103,6 +15107,8 @@ impl<'a> Parser<'a> { 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_summarize() && self.parse_keyword(Keyword::SUMMARIZE) { + SetExpr::Summarize(self.parse_summarize_target()?) } else { return self.expected_ref( "SELECT, VALUES, or a subquery in the query body", @@ -15113,6 +15119,27 @@ impl<'a> Parser<'a> { self.parse_remaining_set_exprs(expr, precedence) } + fn parse_summarize_target(&mut self) -> Result { + if self + .peek_one_of_keywords(&[ + Keyword::SELECT, + Keyword::WITH, + Keyword::VALUES, + Keyword::VALUE, + Keyword::FROM, + Keyword::TABLE, + ]) + .is_some() + || self.peek_token_ref().token == Token::LParen + { + Ok(SummarizeTarget::Query(self.parse_query()?)) + } else { + Ok(SummarizeTarget::Table { + name: self.parse_object_name(false)?, + }) + } + } + /// Parse any extra set expressions that may be present in a query body /// /// (this is its own function to reduce required stack size in debug builds) diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index a338ef7a8..7429b61b8 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -910,3 +910,21 @@ fn test_duckdb_lambda_function() { let sql_transform = "SELECT list_transform([1, 2, 3], lambda x : x * 2)"; duckdb().verified_stmt(sql_transform); } + +#[test] +fn parse_summarize() { + duckdb().verified_stmt("SUMMARIZE users"); + duckdb().verified_stmt(r#"SUMMARIZE "analytics"."events""#); + duckdb().verified_stmt("SUMMARIZE 'users'"); + duckdb().verified_stmt("SUMMARIZE SELECT * FROM users"); + duckdb().verified_stmt("SUMMARIZE FROM users"); + duckdb().verified_stmt("SUMMARIZE VALUES (1.0), (6754950520)"); + duckdb().verified_stmt("SUMMARIZE (SELECT 42 AS answer)"); + duckdb().verified_stmt("SUMMARIZE WITH users AS (SELECT 1 AS id) SELECT * FROM users"); + duckdb().verified_stmt("SELECT column_name FROM (SUMMARIZE SELECT 42 AS answer)"); + + TestedDialects::new(vec![Box::new(GenericDialect {})]) + .parse_sql_statements("SUMMARIZE users") + .expect_err("SUMMARIZE should remain DuckDB-specific"); + duckdb_and_generic().verified_stmt("SELECT summarize FROM jobs"); +}