Skip to content

Commit 580880e

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 c8b7f7c commit 580880e

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
@@ -3361,19 +3361,24 @@ impl Display for ExceptionWhen {
33613361
}
33623362
}
33633363

3364-
/// ANALYZE TABLE statement (Hive-specific)
3364+
/// ANALYZE statement
3365+
///
3366+
/// Supported syntax varies by dialect:
3367+
/// - Hive: `ANALYZE TABLE t [PARTITION (...)] COMPUTE STATISTICS [NOSCAN] [FOR COLUMNS [col1, ...]] [CACHE METADATA]`
3368+
/// - PostgreSQL: `ANALYZE [VERBOSE] [t [(col1, ...)]]`
3369+
/// - General: `ANALYZE [TABLE] t`
33653370
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
33663371
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
33673372
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
33683373
pub struct Analyze {
33693374
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
3370-
/// Name of the table to analyze.
3371-
pub table_name: ObjectName,
3375+
/// Name of the table to analyze. `None` for bare `ANALYZE`.
3376+
pub table_name: Option<ObjectName>,
33723377
/// Optional partition expressions to restrict the analysis.
33733378
pub partitions: Option<Vec<Expr>>,
3374-
/// `true` when analyzing specific columns.
3379+
/// `true` when analyzing specific columns (Hive `FOR COLUMNS` syntax).
33753380
pub for_columns: bool,
3376-
/// Columns to analyze when `for_columns` is `true`.
3381+
/// Columns to analyze.
33773382
pub columns: Vec<Ident>,
33783383
/// Whether to cache metadata before analyzing.
33793384
pub cache_metadata: bool,
@@ -3387,22 +3392,21 @@ pub struct Analyze {
33873392

33883393
impl fmt::Display for Analyze {
33893394
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3390-
write!(
3391-
f,
3392-
"ANALYZE{}{table_name}",
3395+
write!(f, "ANALYZE")?;
3396+
if let Some(ref table_name) = self.table_name {
33933397
if self.has_table_keyword {
3394-
" TABLE "
3395-
} else {
3396-
" "
3397-
},
3398-
table_name = self.table_name
3399-
)?;
3398+
write!(f, " TABLE")?;
3399+
}
3400+
write!(f, " {table_name}")?;
3401+
}
3402+
if !self.for_columns && !self.columns.is_empty() {
3403+
write!(f, " ({})", display_comma_separated(&self.columns))?;
3404+
}
34003405
if let Some(ref parts) = self.partitions {
34013406
if !parts.is_empty() {
34023407
write!(f, " PARTITION ({})", display_comma_separated(parts))?;
34033408
}
34043409
}
3405-
34063410
if self.compute_statistics {
34073411
write!(f, " COMPUTE STATISTICS")?;
34083412
}

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
@@ -1195,13 +1195,21 @@ impl<'a> Parser<'a> {
11951195
/// Parse `ANALYZE` statement.
11961196
pub fn parse_analyze(&mut self) -> Result<Analyze, ParserError> {
11971197
let has_table_keyword = self.parse_keyword(Keyword::TABLE);
1198-
let table_name = self.parse_object_name(false)?;
1198+
let table_name = self
1199+
.maybe_parse(|parser| parser.parse_object_name(false))?;
11991200
let mut for_columns = false;
12001201
let mut cache_metadata = false;
12011202
let mut noscan = false;
12021203
let mut partitions = None;
12031204
let mut compute_statistics = false;
12041205
let mut columns = vec![];
1206+
1207+
// PostgreSQL syntax: ANALYZE t (col1, col2)
1208+
if table_name.is_some() && self.consume_token(&Token::LParen) {
1209+
columns = self.parse_comma_separated(|p| p.parse_identifier())?;
1210+
self.expect_token(&Token::RParen)?;
1211+
}
1212+
12051213
loop {
12061214
match self.parse_one_of_keywords(&[
12071215
Keyword::PARTITION,

tests/sqlparser_postgres.rs

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

0 commit comments

Comments
 (0)