Skip to main content

sqruff_lib_dialects/
db2.rs

1use itertools::Itertools;
2use sqruff_lib_core::dialects::Dialect;
3use sqruff_lib_core::dialects::init::DialectKind;
4use sqruff_lib_core::dialects::syntax::SyntaxKind;
5use sqruff_lib_core::helpers::{Config, ToMatchable};
6use sqruff_lib_core::parser::grammar::Ref;
7use sqruff_lib_core::parser::grammar::anyof::{AnyNumberOf, one_of};
8use sqruff_lib_core::parser::grammar::sequence::{Bracketed, Sequence};
9use sqruff_lib_core::parser::lexer::Matcher;
10use sqruff_lib_core::parser::node_matcher::NodeMatcher;
11use sqruff_lib_core::parser::parsers::RegexParser;
12use sqruff_lib_core::parser::segments::generator::SegmentGenerator;
13
14use crate::db2_keywords::UNRESERVED_KEYWORDS;
15
16use sqruff_lib_core::dialects::init::DialectConfig;
17use sqruff_lib_core::value::Value;
18
19sqruff_lib_core::dialect_config!(Db2DialectConfig {});
20
21pub fn dialect(config: Option<&Value>) -> Dialect {
22    let _dialect_config: Db2DialectConfig =
23        config.map(Db2DialectConfig::from_value).unwrap_or_default();
24
25    raw_dialect().config(|dialect| dialect.expand())
26}
27
28pub fn raw_dialect() -> Dialect {
29    let mut db2_dialect = super::ansi::dialect(None);
30    db2_dialect.name = DialectKind::Db2;
31
32    for kw in UNRESERVED_KEYWORDS {
33        db2_dialect.add_keyword_to_set("unreserved_keywords", kw);
34    }
35
36    // DB2 allows # in field names, and doesn't use it as a comment.
37    db2_dialect.patch_lexer_matchers(vec![
38        // Remove hash comments — DB2 only uses -- for inline comments.
39        Matcher::regex("inline_comment", r"(--)[^\n]*", SyntaxKind::InlineComment),
40        // Allow # in word tokens for identifiers.
41        Matcher::regex("word", r"[0-9a-zA-Z_#]+", SyntaxKind::Word),
42    ]);
43
44    db2_dialect.add([
45        // DB2 allows # in naked identifiers.
46        (
47            "NakedIdentifierSegment".into(),
48            SegmentGenerator::new(|dialect| {
49                let reserved_keywords = dialect.sets("reserved_keywords");
50                let pattern = reserved_keywords.iter().join("|");
51                let anti_template = format!("^({pattern})$");
52
53                RegexParser::new("[A-Z0-9_#]*[A-Z#][A-Z0-9_#]*", SyntaxKind::NakedIdentifier)
54                    .anti_template(&anti_template)
55                    .to_matchable()
56            })
57            .into(),
58        ),
59        // DB2 PostFunctionGrammar: OVER or WITHIN GROUP (no FILTER).
60        (
61            "PostFunctionGrammar".into(),
62            one_of(vec![
63                Ref::new("OverClauseSegment").to_matchable(),
64                Ref::new("WithinGroupClauseSegment").to_matchable(),
65            ])
66            .to_matchable()
67            .into(),
68        ),
69        // DB2 Expression_C_Grammar: adds duration expressions (e.g. 1 DAYS, 1 DAY).
70        (
71            "Expression_C_Grammar".into(),
72            one_of(vec![
73                Sequence::new(vec![
74                    Ref::keyword("EXISTS").to_matchable(),
75                    Bracketed::new(vec![Ref::new("SelectableGrammar").to_matchable()])
76                        .to_matchable(),
77                ])
78                .to_matchable(),
79                Sequence::new(vec![
80                    one_of(vec![
81                        Ref::new("Expression_D_Grammar").to_matchable(),
82                        Ref::new("CaseExpressionSegment").to_matchable(),
83                    ])
84                    .to_matchable(),
85                    AnyNumberOf::new(vec![Ref::new("TimeZoneGrammar").to_matchable()])
86                        .config(|this| this.optional())
87                        .to_matchable(),
88                ])
89                .to_matchable(),
90                Ref::new("ShorthandCastSegment").to_matchable(),
91                Sequence::new(vec![
92                    Ref::new("NumericLiteralSegment").to_matchable(),
93                    one_of(vec![
94                        Ref::keyword("DAYS").to_matchable(),
95                        Ref::keyword("DAY").to_matchable(),
96                    ])
97                    .to_matchable(),
98                ])
99                .to_matchable(),
100            ])
101            .config(|this| this.terminators = vec![Ref::new("CommaSegment").to_matchable()])
102            .to_matchable()
103            .into(),
104        ),
105        // WithinGroupClauseSegment for DB2 window functions.
106        (
107            "WithinGroupClauseSegment".into(),
108            NodeMatcher::new(SyntaxKind::WithingroupClause, |_| {
109                Sequence::new(vec![
110                    Ref::keyword("WITHIN").to_matchable(),
111                    Ref::keyword("GROUP").to_matchable(),
112                    Bracketed::new(vec![
113                        Ref::new("OrderByClauseSegment").optional().to_matchable(),
114                    ])
115                    .to_matchable(),
116                ])
117                .to_matchable()
118            })
119            .to_matchable()
120            .into(),
121        ),
122    ]);
123
124    db2_dialect
125}