Skip to content

Commit 67ed5da

Browse files
MazterQyouclaude
authored andcommitted
feat: Add APPROXIMATE function keyword support
Reimplemented on top of upstream v0.62.0: the original patch added an `approximate` flag to the (now heavily refactored) `Function` struct, so it could not be cherry-picked cleanly. Parsing is gated to a `<function>(` that immediately follows `APPROXIMATE`, so a bare `approximate` identifier still parses as a column name. Covers `APPROXIMATE COUNT(DISTINCT x)` and `APPROXIMATE PERCENTILE_DISC(...) WITHIN GROUP (...)` for Redshift/PostgreSQL/Generic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7fbbbbd commit 67ed5da

16 files changed

Lines changed: 118 additions & 10 deletions

src/ast/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8092,10 +8092,16 @@ pub struct Function {
80928092
/// <aggregate_function>(expression) WITHIN GROUP (ORDER BY key [ASC | DESC], ...)
80938093
/// ```
80948094
pub within_group: Vec<OrderByExpr>,
8095+
/// Redshift `APPROXIMATE` option for some functions, e.g.
8096+
/// `APPROXIMATE COUNT(DISTINCT x)`.
8097+
pub approximate: bool,
80958098
}
80968099

80978100
impl fmt::Display for Function {
80988101
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
8102+
if self.approximate {
8103+
write!(f, "APPROXIMATE ")?;
8104+
}
80998105
if self.uses_odbc_syntax {
81008106
write!(f, "{{fn ")?;
81018107
}

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1739,6 +1739,7 @@ impl Spanned for Function {
17391739
null_treatment: _, // enum
17401740
over: _, // todo
17411741
within_group,
1742+
approximate: _,
17421743
} = self;
17431744

17441745
union_spans(

src/ast/visitor.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,7 @@ where
598598
/// over: None,
599599
/// parameters: FunctionArguments::None,
600600
/// within_group: vec![],
601+
/// approximate: false,
601602
/// });
602603
/// }
603604
/// ControlFlow::<()>::Continue(())

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ define_keywords!(
121121
APPLICATION,
122122
APPLY,
123123
APPLYBUDGET,
124+
APPROXIMATE,
124125
ARCHIVE,
125126
ARE,
126127
ARRAY,

src/parser/mod.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,6 +1533,7 @@ impl<'a> Parser<'a> {
15331533
filter: None,
15341534
over: None,
15351535
within_group: vec![],
1536+
approximate: false,
15361537
})))
15371538
}
15381539
Keyword::CURRENT_TIMESTAMP
@@ -1595,6 +1596,7 @@ impl<'a> Parser<'a> {
15951596
null_treatment: None,
15961597
over: None,
15971598
within_group: vec![],
1599+
approximate: false,
15981600
})))
15991601
}
16001602
Keyword::NOT => Ok(Some(self.parse_not()?)),
@@ -1780,6 +1782,31 @@ impl<'a> Parser<'a> {
17801782
return Ok(expr);
17811783
}
17821784

1785+
// Redshift `APPROXIMATE <function>` option, e.g. `APPROXIMATE COUNT(DISTINCT x)`
1786+
// or `APPROXIMATE PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY x)`. The keyword is
1787+
// only meaningful immediately before a function call, so we require a following
1788+
// `<word> (` to avoid treating a bare `approximate` identifier as this option.
1789+
if dialect_of!(self is RedshiftSqlDialect | PostgreSqlDialect | GenericDialect)
1790+
&& matches!(
1791+
self.peek_tokens(),
1792+
[
1793+
Token::Word(Word {
1794+
keyword: Keyword::APPROXIMATE,
1795+
..
1796+
}),
1797+
Token::Word(_),
1798+
Token::LParen,
1799+
]
1800+
)
1801+
{
1802+
self.expect_keyword_is(Keyword::APPROXIMATE)?;
1803+
let mut expr = self.parse_prefix()?;
1804+
if let Expr::Function(func) = &mut expr {
1805+
func.approximate = true;
1806+
}
1807+
return Ok(expr);
1808+
}
1809+
17831810
// Cache some dialect properties to avoid lifetime issues with the
17841811
// next_token reference.
17851812

@@ -2455,6 +2482,7 @@ impl<'a> Parser<'a> {
24552482
null_treatment: None,
24562483
over: None,
24572484
within_group: vec![],
2485+
approximate: false,
24582486
});
24592487
}
24602488

@@ -2523,6 +2551,7 @@ impl<'a> Parser<'a> {
25232551
filter,
25242552
over,
25252553
within_group,
2554+
approximate: false,
25262555
})
25272556
}
25282557

@@ -2558,6 +2587,7 @@ impl<'a> Parser<'a> {
25582587
over: None,
25592588
null_treatment: None,
25602589
within_group: vec![],
2590+
approximate: false,
25612591
}))
25622592
}
25632593

@@ -11514,6 +11544,7 @@ impl<'a> Parser<'a> {
1151411544
filter: None,
1151511545
null_treatment: None,
1151611546
within_group: vec![],
11547+
approximate: false,
1151711548
}))
1151811549
}
1151911550
}

src/test_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ pub fn call(function: &str, args: impl IntoIterator<Item = Expr>) -> Expr {
453453
null_treatment: None,
454454
over: None,
455455
within_group: vec![],
456+
approximate: false,
456457
})
457458
}
458459

tests/sqlparser_bigquery.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2249,6 +2249,7 @@ fn parse_map_access_expr() {
22492249
null_treatment: None,
22502250
over: None,
22512251
within_group: vec![],
2252+
approximate: false,
22522253
uses_odbc_syntax: false,
22532254
}),
22542255
}),

tests/sqlparser_clickhouse.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ fn parse_delimited_identifiers() {
204204
filter: None,
205205
over: None,
206206
within_group: vec![],
207+
approximate: false,
207208
}),
208209
expr_from_projection(&select.projection[1]),
209210
);
@@ -898,6 +899,7 @@ fn parse_create_table_with_variant_default_expressions() {
898899
filter: None,
899900
over: None,
900901
within_group: vec![],
902+
approximate: false,
901903
}))
902904
}],
903905
},
@@ -919,6 +921,7 @@ fn parse_create_table_with_variant_default_expressions() {
919921
filter: None,
920922
over: None,
921923
within_group: vec![],
924+
approximate: false,
922925
})))
923926
}],
924927
},
@@ -950,6 +953,7 @@ fn parse_create_table_with_variant_default_expressions() {
950953
filter: None,
951954
over: None,
952955
within_group: vec![],
956+
approximate: false,
953957
}))
954958
}],
955959
}

tests/sqlparser_common.rs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1367,7 +1367,8 @@ fn parse_select_count_wildcard() {
13671367
null_treatment: None,
13681368
filter: None,
13691369
over: None,
1370-
within_group: vec![]
1370+
within_group: vec![],
1371+
approximate: false,
13711372
}),
13721373
expr_from_projection(only(&select.projection))
13731374
);
@@ -1392,6 +1393,7 @@ fn parse_select_count_distinct() {
13921393
}),
13931394
null_treatment: None,
13941395
within_group: vec![],
1396+
approximate: false,
13951397
filter: None,
13961398
over: None
13971399
}),
@@ -2997,7 +2999,8 @@ fn parse_select_having() {
29972999
null_treatment: None,
29983000
filter: None,
29993001
over: None,
3000-
within_group: vec![]
3002+
within_group: vec![],
3003+
approximate: false,
30013004
})),
30023005
op: BinaryOperator::Gt,
30033006
right: Box::new(Expr::value(number("1"))),
@@ -3040,7 +3043,8 @@ fn parse_select_qualify() {
30403043
}],
30413044
window_frame: None,
30423045
})),
3043-
within_group: vec![]
3046+
within_group: vec![],
3047+
approximate: false,
30443048
})),
30453049
op: BinaryOperator::Eq,
30463050
right: Box::new(Expr::value(number("1"))),
@@ -3499,7 +3503,8 @@ fn parse_listagg() {
34993503
},
35003504
with_fill: None,
35013505
},
3502-
]
3506+
],
3507+
approximate: false,
35033508
}),
35043509
expr_from_projection(only(&select.projection))
35053510
);
@@ -5648,7 +5653,8 @@ fn parse_named_argument_function() {
56485653
null_treatment: None,
56495654
filter: None,
56505655
over: None,
5651-
within_group: vec![]
5656+
within_group: vec![],
5657+
approximate: false,
56525658
}),
56535659
expr_from_projection(only(&select.projection))
56545660
);
@@ -5689,6 +5695,7 @@ fn parse_named_argument_function_with_eq_operator() {
56895695
filter: None,
56905696
over: None,
56915697
within_group: vec![],
5698+
approximate: false,
56925699
}),
56935700
expr_from_projection(only(&select.projection))
56945701
);
@@ -5761,6 +5768,7 @@ fn parse_window_functions() {
57615768
window_frame: None,
57625769
})),
57635770
within_group: vec![],
5771+
approximate: false,
57645772
}),
57655773
expr_from_projection(&select.projection[0])
57665774
);
@@ -5901,6 +5909,7 @@ fn test_parse_named_window() {
59015909
span: Span::empty(),
59025910
})),
59035911
within_group: vec![],
5912+
approximate: false,
59045913
}),
59055914
alias: Ident {
59065915
value: "min1".to_string(),
@@ -5936,6 +5945,7 @@ fn test_parse_named_window() {
59365945
span: Span::empty(),
59375946
})),
59385947
within_group: vec![],
5948+
approximate: false,
59395949
}),
59405950
alias: Ident {
59415951
value: "max1".to_string(),
@@ -10639,6 +10649,7 @@ fn parse_time_functions() {
1063910649
filter: None,
1064010650
over: None,
1064110651
within_group: vec![],
10652+
approximate: false,
1064210653
};
1064310654
assert_eq!(
1064410655
&Expr::Function(select_localtime_func_call_ast.clone()),
@@ -12274,6 +12285,7 @@ fn parse_call() {
1227412285
null_treatment: None,
1227512286
over: None,
1227612287
within_group: vec![],
12288+
approximate: false,
1227712289
})
1227812290
);
1227912291
}
@@ -12743,6 +12755,7 @@ fn parse_map_access_expr() {
1274312755
null_treatment: None,
1274412756
over: None,
1274512757
within_group: vec![],
12758+
approximate: false,
1274612759
uses_odbc_syntax: false,
1274712760
}),
1274812761
}),
@@ -13084,6 +13097,7 @@ fn test_selective_aggregation() {
1308413097
))))),
1308513098
over: None,
1308613099
within_group: vec![],
13100+
approximate: false,
1308713101
null_treatment: None
1308813102
})),
1308913103
SelectItem::ExprWithAlias {
@@ -13109,7 +13123,8 @@ fn test_selective_aggregation() {
1310913123
})),
1311013124
null_treatment: None,
1311113125
over: None,
13112-
within_group: vec![]
13126+
within_group: vec![],
13127+
approximate: false,
1311313128
}),
1311413129
alias: Ident::new("agg2")
1311513130
},
@@ -15531,7 +15546,8 @@ fn parse_composite_access_expr() {
1553115546
null_treatment: None,
1553215547
filter: None,
1553315548
over: None,
15534-
within_group: vec![]
15549+
within_group: vec![],
15550+
approximate: false,
1553515551
})),
1553615552
access_chain: vec![AccessExpr::Dot(Expr::Identifier(Ident::new("b")))]
1553715553
}
@@ -15555,7 +15571,8 @@ fn parse_composite_access_expr() {
1555515571
null_treatment: None,
1555615572
filter: None,
1555715573
over: None,
15558-
within_group: vec![]
15574+
within_group: vec![],
15575+
approximate: false,
1555915576
})),
1556015577
access_chain: vec![
1556115578
AccessExpr::Dot(Expr::Identifier(Ident::new("b"))),
@@ -15582,6 +15599,7 @@ fn parse_composite_access_expr() {
1558215599
filter: None,
1558315600
over: None,
1558415601
within_group: vec![],
15602+
approximate: false,
1558515603
})),
1558615604
access_chain: vec![AccessExpr::Dot(Expr::Identifier(Ident::new("b")))],
1558715605
};

tests/sqlparser_duckdb.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,7 @@ fn test_duckdb_named_argument_function_with_assignment_operator() {
662662
filter: None,
663663
over: None,
664664
within_group: vec![],
665+
approximate: false,
665666
}),
666667
expr_from_projection(only(&select.projection))
667668
);

0 commit comments

Comments
 (0)