35 |
36 |
|
""" |
36 |
37 |
|
|
37 |
38 |
|
groups = ("all", "core") |
38 |
|
- |
crawl_behaviour = SegmentSeekerCrawler({"select_clause"}) |
|
39 |
+ |
crawl_behaviour = SegmentSeekerCrawler({"select_clause", "function"}) |
39 |
40 |
|
|
40 |
41 |
|
def _eval(self, context: RuleContext) -> Optional[LintResult]: |
41 |
42 |
|
"""Looking for DISTINCT before a bracket. |
42 |
43 |
|
|
43 |
44 |
|
Look for DISTINCT keyword immediately followed by open parenthesis. |
44 |
45 |
|
""" |
45 |
|
- |
# We trigger on `select_clause` and look for `select_clause_modifier` |
46 |
|
- |
assert context.segment.is_type("select_clause") |
|
46 |
+ |
seq = None |
|
47 |
+ |
anchor = None |
47 |
48 |
|
children = FunctionalContext(context).segment.children() |
48 |
|
- |
modifier = children.select(sp.is_type("select_clause_modifier")) |
49 |
|
- |
first_element = children.select(sp.is_type("select_clause_element")).first() |
50 |
|
- |
if not modifier or not first_element: |
51 |
|
- |
return None |
52 |
|
- |
# is the first element only an expression with only brackets? |
53 |
|
- |
expression = ( |
54 |
|
- |
first_element.children(sp.is_type("expression")).first() or first_element |
55 |
|
- |
) |
56 |
|
- |
bracketed = expression.children(sp.is_type("bracketed")).first() |
57 |
|
- |
if bracketed: |
58 |
|
- |
# If there's nothing else in the expression, remove the brackets. |
59 |
|
- |
if len(expression[0].segments) == 1: |
60 |
|
- |
# Remove the brackets and strip any meta segments. |
61 |
|
- |
anchor = bracketed.get() |
62 |
|
- |
assert anchor |
63 |
|
- |
seq = ReflowSequence.from_around_target( |
64 |
|
- |
anchor, |
65 |
|
- |
context.parent_stack[0], |
66 |
|
- |
config=context.config, |
67 |
|
- |
sides="before", |
68 |
|
- |
).replace(anchor, self.filter_meta(anchor.segments)[1:-1]) |
69 |
|
- |
# Otherwise, still make sure there's a space after the DISTINCT. |
70 |
|
- |
else: |
71 |
|
- |
anchor = modifier[0] |
72 |
|
- |
seq = ReflowSequence.from_around_target( |
73 |
|
- |
modifier[0], |
74 |
|
- |
context.parent_stack[0], |
75 |
|
- |
config=context.config, |
76 |
|
- |
sides="after", |
77 |
|
- |
) |
|
49 |
+ |
if context.segment.is_type("select_clause"): |
|
50 |
+ |
# Look for `select_clause_modifier` |
|
51 |
+ |
modifier = children.select(sp.is_type("select_clause_modifier")) |
|
52 |
+ |
first_element = children.select(sp.is_type("select_clause_element")).first() |
|
53 |
+ |
expression = ( |
|
54 |
+ |
first_element.children(sp.is_type("expression")).first() |
|
55 |
+ |
or first_element |
|
56 |
+ |
) |
|
57 |
+ |
bracketed = expression.children(sp.is_type("bracketed")).first() |
|
58 |
+ |
# is the first element only an expression with only brackets? |
|
59 |
+ |
if modifier and bracketed: |
|
60 |
+ |
# If there's nothing else in the expression, remove the brackets. |
|
61 |
+ |
if len(expression[0].segments) == 1: |
|
62 |
+ |
anchor, seq = self._remove_unneeded_brackets(context, bracketed) |
|
63 |
+ |
# Otherwise, still make sure there's a space after the DISTINCT. |
|
64 |
+ |
else: |
|
65 |
+ |
anchor = modifier[0] |
|
66 |
+ |
seq = ReflowSequence.from_around_target( |
|
67 |
+ |
modifier[0], |
|
68 |
+ |
context.parent_stack[0], |
|
69 |
+ |
config=context.config, |
|
70 |
+ |
sides="after", |
|
71 |
+ |
) |
|
72 |
+ |
elif context.segment.is_type("function"): |
|
73 |
+ |
# Look for a function call DISTINCT() whose parent is an expression |
|
74 |
+ |
# with a single child. |
|
75 |
+ |
anchor = context.parent_stack[-1] |
|
76 |
+ |
if not anchor.is_type("expression") or len(anchor.segments) != 1: |
|
77 |
+ |
return None |
|
78 |
+ |
function_name = children.select(sp.is_type("function_name")).first() |
|
79 |
+ |
bracketed = children.first(sp.is_type("bracketed")) |
|
80 |
+ |
if ( |
|
81 |
+ |
not function_name |
|
82 |
+ |
or function_name[0].raw_upper != "DISTINCT" |
|
83 |
+ |
or not bracketed |
|
84 |
+ |
): |
|
85 |
+ |
return None |
|
86 |
+ |
# Using ReflowSequence here creates an unneeded space between CONCAT |
|
87 |
+ |
# and "(" in the test case test_fail_distinct_concat_inside_count: |
|
88 |
+ |
# SELECT COUNT(DISTINCT(CONCAT(col1, '-', col2, '-', col3))) |
|
89 |
+ |
# |
|
90 |
+ |
# seq = ReflowSequence.from_around_target( |
|
91 |
+ |
# anchor, |
|
92 |
+ |
# context.parent_stack[0], |
|
93 |
+ |
# config=context.config, |
|
94 |
+ |
# ).replace( |
|
95 |
+ |
# anchor, |
|
96 |
+ |
# (KeywordSegment("DISTINCT"), WhitespaceSegment()) |
|
97 |
+ |
# + self.filter_meta(bracketed[0].segments)[1:-1], |
|
98 |
+ |
# ) |
|
99 |
+ |
# Do this until we have a fix for the above. |
|
100 |
+ |
return LintResult( |
|
101 |
+ |
anchor=anchor, |
|
102 |
+ |
fixes=[ |
|
103 |
+ |
LintFix.replace( |
|
104 |
+ |
anchor, |
|
105 |
+ |
(KeywordSegment("DISTINCT"), WhitespaceSegment()) |
|
106 |
+ |
+ self.filter_meta(bracketed[0].segments)[1:-1], |
|
107 |
+ |
) |
|
108 |
+ |
], |
|
109 |
+ |
) |
|
110 |
+ |
if seq and anchor: |
78 |
111 |
|
# Get modifications. |
79 |
112 |
|
fixes = seq.respace().get_fixes() |
80 |
113 |
|
if fixes: |