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");
+}