Skip to content

Commit 0924f3a

Browse files
authored
PostgreSQL: Support PostgreSQL ANALYZE with optional table and column (apache#2187)
Signed-off-by: Guan-Ming (Wesley) Chiu <105915352+guan404ming@users.noreply.github.com>
1 parent 23acd23 commit 0924f3a

File tree

4 files changed

+55
-17
lines changed

4 files changed

+55
-17
lines changed

src/ast/mod.rs

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

3329-
/// ANALYZE TABLE statement (Hive-specific)
3329+
/// ANALYZE statement
3330+
///
3331+
/// Supported syntax varies by dialect:
3332+
/// - Hive: `ANALYZE TABLE t [PARTITION (...)] COMPUTE STATISTICS [NOSCAN] [FOR COLUMNS [col1, ...]] [CACHE METADATA]`
3333+
/// - PostgreSQL: `ANALYZE [VERBOSE] [t [(col1, ...)]]` See <https://www.postgresql.org/docs/current/sql-analyze.html>
3334+
/// - General: `ANALYZE [TABLE] t`
33303335
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
33313336
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
33323337
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
33333338
pub struct Analyze {
33343339
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
3335-
/// Name of the table to analyze.
3336-
pub table_name: ObjectName,
3340+
/// Name of the table to analyze. `None` for bare `ANALYZE`.
3341+
pub table_name: Option<ObjectName>,
33373342
/// Optional partition expressions to restrict the analysis.
33383343
pub partitions: Option<Vec<Expr>>,
3339-
/// `true` when analyzing specific columns.
3344+
/// `true` when analyzing specific columns (Hive `FOR COLUMNS` syntax).
33403345
pub for_columns: bool,
3341-
/// Columns to analyze when `for_columns` is `true`.
3346+
/// Columns to analyze.
33423347
pub columns: Vec<Ident>,
33433348
/// Whether to cache metadata before analyzing.
33443349
pub cache_metadata: bool,
@@ -3352,22 +3357,21 @@ pub struct Analyze {
33523357

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

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: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1193,13 +1193,20 @@ 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.maybe_parse(|parser| parser.parse_object_name(false))?;
11971197
let mut for_columns = false;
11981198
let mut cache_metadata = false;
11991199
let mut noscan = false;
12001200
let mut partitions = None;
12011201
let mut compute_statistics = false;
12021202
let mut columns = vec![];
1203+
1204+
// PostgreSQL syntax: ANALYZE t (col1, col2)
1205+
if table_name.is_some() && self.consume_token(&Token::LParen) {
1206+
columns = self.parse_comma_separated(|p| p.parse_identifier())?;
1207+
self.expect_token(&Token::RParen)?;
1208+
}
1209+
12031210
loop {
12041211
match self.parse_one_of_keywords(&[
12051212
Keyword::PARTITION,

tests/sqlparser_postgres.rs

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

0 commit comments

Comments
 (0)