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
34 changes: 19 additions & 15 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3361,19 +3361,24 @@ impl Display for ExceptionWhen {
}
}

/// ANALYZE TABLE statement (Hive-specific)
/// ANALYZE statement
///
/// Supported syntax varies by dialect:
/// - Hive: `ANALYZE TABLE t [PARTITION (...)] COMPUTE STATISTICS [NOSCAN] [FOR COLUMNS [col1, ...]] [CACHE METADATA]`
/// - PostgreSQL: `ANALYZE [VERBOSE] [t [(col1, ...)]]`
/// - General: `ANALYZE [TABLE] t`
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct Analyze {
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
/// Name of the table to analyze.
pub table_name: ObjectName,
/// Name of the table to analyze. `None` for bare `ANALYZE`.
pub table_name: Option<ObjectName>,
/// Optional partition expressions to restrict the analysis.
pub partitions: Option<Vec<Expr>>,
/// `true` when analyzing specific columns.
/// `true` when analyzing specific columns (Hive `FOR COLUMNS` syntax).
pub for_columns: bool,
/// Columns to analyze when `for_columns` is `true`.
/// Columns to analyze.
pub columns: Vec<Ident>,
/// Whether to cache metadata before analyzing.
pub cache_metadata: bool,
Expand All @@ -3387,22 +3392,21 @@ pub struct Analyze {

impl fmt::Display for Analyze {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"ANALYZE{}{table_name}",
write!(f, "ANALYZE")?;
if let Some(ref table_name) = self.table_name {
if self.has_table_keyword {
" TABLE "
} else {
" "
},
table_name = self.table_name
)?;
write!(f, " TABLE")?;
}
write!(f, " {table_name}")?;
}
if !self.for_columns && !self.columns.is_empty() {
write!(f, " ({})", display_comma_separated(&self.columns))?;
}
if let Some(ref parts) = self.partitions {
if !parts.is_empty() {
write!(f, " PARTITION ({})", display_comma_separated(parts))?;
}
}

if self.compute_statistics {
write!(f, " COMPUTE STATISTICS")?;
}
Expand Down
4 changes: 3 additions & 1 deletion src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,9 @@ impl Spanned for ConstraintCharacteristics {
impl Spanned for Analyze {
fn span(&self) -> Span {
union_spans(
core::iter::once(self.table_name.span())
self.table_name
.iter()
.map(|t| t.span())
.chain(
self.partitions
.iter()
Expand Down
9 changes: 8 additions & 1 deletion src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1195,13 +1195,20 @@ impl<'a> Parser<'a> {
/// Parse `ANALYZE` statement.
pub fn parse_analyze(&mut self) -> Result<Analyze, ParserError> {
let has_table_keyword = self.parse_keyword(Keyword::TABLE);
let table_name = self.parse_object_name(false)?;
let table_name = self.maybe_parse(|parser| parser.parse_object_name(false))?;
let mut for_columns = false;
let mut cache_metadata = false;
let mut noscan = false;
let mut partitions = None;
let mut compute_statistics = false;
let mut columns = vec![];

// PostgreSQL syntax: ANALYZE t (col1, col2)
if table_name.is_some() && self.consume_token(&Token::LParen) {
columns = self.parse_comma_separated(|p| p.parse_identifier())?;
self.expect_token(&Token::RParen)?;
}

loop {
match self.parse_one_of_keywords(&[
Keyword::PARTITION,
Expand Down
25 changes: 25 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8501,3 +8501,28 @@ fn parse_create_table_partition_of_errors() {
"Expected error about empty TO list, got: {err}"
);
}

#[test]
fn parse_pg_analyze() {
// Bare ANALYZE
pg_and_generic().verified_stmt("ANALYZE");

// ANALYZE with table name
pg_and_generic().verified_stmt("ANALYZE t");

// ANALYZE with column specification
pg_and_generic().verified_stmt("ANALYZE t (col1, col2)");

// Verify AST for column specification
let stmt = pg().verified_stmt("ANALYZE t (col1, col2)");
match &stmt {
Statement::Analyze(analyze) => {
assert_eq!(analyze.table_name.as_ref().unwrap().to_string(), "t");
assert_eq!(analyze.columns.len(), 2);
assert_eq!(analyze.columns[0].to_string(), "col1");
assert_eq!(analyze.columns[1].to_string(), "col2");
assert!(!analyze.for_columns);
}
_ => panic!("Expected Analyze, got: {stmt:?}"),
}
}