Skip to content

Commit 1be5c1e

Browse files
committed
Support PostgreSQL ANALYZE with optional table and column
Signed-off-by: Guan-Ming (Wesley) Chiu <105915352+guan404ming@users.noreply.github.com>
1 parent 3ac5670 commit 1be5c1e

4 files changed

Lines changed: 56 additions & 17 deletions

File tree

src/ast/mod.rs

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3325,19 +3325,24 @@ impl Display for ExceptionWhen {
33253325
}
33263326
}
33273327

3328-
/// ANALYZE TABLE statement (Hive-specific)
3328+
/// ANALYZE statement
3329+
///
3330+
/// Supported syntax varies by dialect:
3331+
/// - Hive: `ANALYZE TABLE t [PARTITION (...)] COMPUTE STATISTICS [NOSCAN] [FOR COLUMNS [col1, ...]] [CACHE METADATA]`
3332+
/// - PostgreSQL: `ANALYZE [VERBOSE] [t [(col1, ...)]]`
3333+
/// - General: `ANALYZE [TABLE] t`
33293334
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
33303335
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
33313336
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
33323337
pub struct Analyze {
33333338
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
3334-
/// Name of the table to analyze.
3335-
pub table_name: ObjectName,
3339+
/// Name of the table to analyze. `None` for bare `ANALYZE`.
3340+
pub table_name: Option<ObjectName>,
33363341
/// Optional partition expressions to restrict the analysis.
33373342
pub partitions: Option<Vec<Expr>>,
3338-
/// `true` when analyzing specific columns.
3343+
/// `true` when analyzing specific columns (Hive `FOR COLUMNS` syntax).
33393344
pub for_columns: bool,
3340-
/// Columns to analyze when `for_columns` is `true`.
3345+
/// Columns to analyze.
33413346
pub columns: Vec<Ident>,
33423347
/// Whether to cache metadata before analyzing.
33433348
pub cache_metadata: bool,
@@ -3351,22 +3356,21 @@ pub struct Analyze {
33513356

33523357
impl fmt::Display for Analyze {
33533358
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3354-
write!(
3355-
f,
3356-
"ANALYZE{}{table_name}",
3359+
write!(f, "ANALYZE")?;
3360+
if let Some(ref table_name) = self.table_name {
33573361
if self.has_table_keyword {
3358-
" TABLE "
3359-
} else {
3360-
" "
3361-
},
3362-
table_name = self.table_name
3363-
)?;
3362+
write!(f, " TABLE")?;
3363+
}
3364+
write!(f, " {table_name}")?;
3365+
}
3366+
if !self.for_columns && !self.columns.is_empty() {
3367+
write!(f, " ({})", display_comma_separated(&self.columns))?;
3368+
}
33643369
if let Some(ref parts) = self.partitions {
33653370
if !parts.is_empty() {
33663371
write!(f, " PARTITION ({})", display_comma_separated(parts))?;
33673372
}
33683373
}
3369-
33703374
if self.compute_statistics {
33713375
write!(f, " COMPUTE STATISTICS")?;
33723376
}

src/ast/spans.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,9 @@ impl Spanned for ConstraintCharacteristics {
841841
impl Spanned for Analyze {
842842
fn span(&self) -> Span {
843843
union_spans(
844-
core::iter::once(self.table_name.span())
844+
self.table_name
845+
.iter()
846+
.map(|t| t.span())
845847
.chain(
846848
self.partitions
847849
.iter()

src/parser/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1193,13 +1193,21 @@ impl<'a> Parser<'a> {
11931193
/// Parse `ANALYZE` statement.
11941194
pub fn parse_analyze(&mut self) -> Result<Analyze, ParserError> {
11951195
let has_table_keyword = self.parse_keyword(Keyword::TABLE);
1196-
let table_name = self.parse_object_name(false)?;
1196+
let table_name = self
1197+
.maybe_parse(|parser| parser.parse_object_name(false))?;
11971198
let mut for_columns = false;
11981199
let mut cache_metadata = false;
11991200
let mut noscan = false;
12001201
let mut partitions = None;
12011202
let mut compute_statistics = false;
12021203
let mut columns = vec![];
1204+
1205+
// PostgreSQL syntax: ANALYZE t (col1, col2)
1206+
if table_name.is_some() && self.consume_token(&Token::LParen) {
1207+
columns = self.parse_comma_separated(|p| p.parse_identifier())?;
1208+
self.expect_token(&Token::RParen)?;
1209+
}
1210+
12031211
loop {
12041212
match self.parse_one_of_keywords(&[
12051213
Keyword::PARTITION,

tests/sqlparser_postgres.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8508,3 +8508,28 @@ fn parse_create_table_partition_of_errors() {
85088508
"Expected error about empty TO list, got: {err}"
85098509
);
85108510
}
8511+
8512+
#[test]
8513+
fn parse_pg_analyze() {
8514+
// Bare ANALYZE
8515+
pg_and_generic().verified_stmt("ANALYZE");
8516+
8517+
// ANALYZE with table name
8518+
pg_and_generic().verified_stmt("ANALYZE t");
8519+
8520+
// ANALYZE with column specification
8521+
pg_and_generic().verified_stmt("ANALYZE t (col1, col2)");
8522+
8523+
// Verify AST for column specification
8524+
let stmt = pg().verified_stmt("ANALYZE t (col1, col2)");
8525+
match &stmt {
8526+
Statement::Analyze(analyze) => {
8527+
assert_eq!(analyze.table_name.as_ref().unwrap().to_string(), "t");
8528+
assert_eq!(analyze.columns.len(), 2);
8529+
assert_eq!(analyze.columns[0].to_string(), "col1");
8530+
assert_eq!(analyze.columns[1].to_string(), "col2");
8531+
assert!(!analyze.for_columns);
8532+
}
8533+
_ => panic!("Expected Analyze, got: {stmt:?}"),
8534+
}
8535+
}

0 commit comments

Comments
 (0)