Skip to main content

rigsql_rules/
utils.rs

1use rigsql_core::{Segment, SegmentType};
2
3/// Check if an AliasExpression's children contain an explicit AS keyword.
4pub fn has_as_keyword(children: &[Segment]) -> bool {
5    children.iter().any(|child| {
6        if let Segment::Token(t) = child {
7            t.segment_type == SegmentType::Keyword && t.token.text.eq_ignore_ascii_case("AS")
8        } else {
9            false
10        }
11    })
12}
13
14/// Return the first non-trivia child segment.
15pub fn first_non_trivia(children: &[Segment]) -> Option<&Segment> {
16    children.iter().find(|c| !c.segment_type().is_trivia())
17}
18
19/// Return the last non-trivia child segment.
20pub fn last_non_trivia(children: &[Segment]) -> Option<&Segment> {
21    children
22        .iter()
23        .rev()
24        .find(|c| !c.segment_type().is_trivia())
25}
26
27/// Keywords that should NOT be treated as alias names.
28/// Sorted alphabetically for binary_search.
29const NOT_ALIAS_KEYWORDS: &[&str] = &[
30    "ALTER",
31    "AND",
32    "BEGIN",
33    "BREAK",
34    "CATCH",
35    "CLOSE",
36    "COMMIT",
37    "CONTINUE",
38    "CREATE",
39    "CROSS",
40    "CURSOR",
41    "DEALLOCATE",
42    "DECLARE",
43    "DELETE",
44    "DROP",
45    "ELSE",
46    "END",
47    "EXCEPT",
48    "EXEC",
49    "EXECUTE",
50    "FETCH",
51    "FOR",
52    "FROM",
53    "FULL",
54    "GO",
55    "GOTO",
56    "GROUP",
57    "HAVING",
58    "IF",
59    "INNER",
60    "INSERT",
61    "INTERSECT",
62    "INTO",
63    "JOIN",
64    "LEFT",
65    "LIMIT",
66    "MERGE",
67    "NATURAL",
68    "NEXT",
69    "OFFSET",
70    "ON",
71    "OPEN",
72    "OR",
73    "ORDER",
74    "OUTPUT",
75    "OVER",
76    "PRINT",
77    "RAISERROR",
78    "RETURN",
79    "RETURNING",
80    "RIGHT",
81    "ROLLBACK",
82    "SELECT",
83    "SET",
84    "TABLE",
85    "THEN",
86    "THROW",
87    "TRUNCATE",
88    "TRY",
89    "UNION",
90    "UPDATE",
91    "VALUES",
92    "WHEN",
93    "WHERE",
94    "WHILE",
95    "WITH",
96];
97
98/// Check if the "alias name" in an AliasExpression is actually a misidentified
99/// SQL keyword (e.g. OVER in window functions). Returns true if the alias
100/// looks like a false positive.
101pub fn is_false_alias(children: &[Segment]) -> bool {
102    // The alias name is the last non-trivia child
103    if let Some(Segment::Token(t)) = last_non_trivia(children) {
104        let upper = t.token.text.to_ascii_uppercase();
105        return NOT_ALIAS_KEYWORDS.binary_search(&upper.as_str()).is_ok();
106    }
107    false
108}
109
110/// Find a keyword by case-insensitive name in children. Returns (index, segment).
111pub fn find_keyword_in_children<'a>(
112    children: &'a [Segment],
113    name: &str,
114) -> Option<(usize, &'a Segment)> {
115    children.iter().enumerate().find(|(_, c)| {
116        if let Segment::Token(t) = c {
117            t.segment_type == SegmentType::Keyword && t.token.text.eq_ignore_ascii_case(name)
118        } else {
119            false
120        }
121    })
122}