def _eval(self, context: RuleContext) -> EvalResultType: """Get References and Aliases and allow linting. This rule covers a lot of potential cases of odd usages of references, see the code for each of the potential cases. Subclasses of this rule should override the `_lint_references_and_aliases` method. """ if context.segment.is_type("select_statement"): select_info = get_select_statement_info(context.segment, context.dialect) if not select_info: return None # Work out if we have a parent select function parent_select = None for seg in reversed(context.parent_stack): if seg.is_type("select_statement"): parent_select = seg break # Pass them all to the function that does all the work. # NB: Subclasses of this rules should override the function below return self._lint_references_and_aliases( select_info.table_aliases, select_info.standalone_aliases, select_info.reference_buffer, select_info.col_aliases, select_info.using_cols, parent_select, ) return None
def _eval(self, context: RuleContext) -> EvalResultType: violations: List[LintResult] = [] if context.segment.is_type("select_statement"): # Exit early if the SELECT does not define any aliases. select_info = get_select_statement_info(context.segment, context.dialect) if not select_info or not select_info.table_aliases: return None # Analyze the SELECT. crawler = SelectCrawler(context.segment, context.dialect, query_class=L025Query) query: L025Query = cast(L025Query, crawler.query_tree) self._analyze_table_aliases(query, context.dialect) alias: AliasInfo for alias in query.aliases: if alias.aliased and alias.ref_str not in query.tbl_refs: # Unused alias. Report and fix. violations.append(self._report_unused_alias(alias)) return violations or None
def select_info(self): """Returns SelectStatementColumnsAndTables on the SELECT.""" if self.selectable.is_type("select_statement"): return get_select_statement_info( self.selectable, self.dialect, early_exit=False ) else: # values_clause # This is a bit dodgy, but a very useful abstraction. Here, we # interpret a values_clause segment as if it were a SELECT. Someday, # we may need to tweak this, e.g. perhaps add a separate QueryType # for this (depending on the needs of the rules that use it. # # For more info on the syntax and behavior of VALUES and its # similarity to a SELECT statement with literal values (no table # source), see the "Examples" section of the Postgres docs page: # (https://www.postgresql.org/docs/8.2/sql-values.html). values = Segments(self.selectable) alias_expression = values.children().first(sp.is_type("alias_expression")) name = alias_expression.children().first( sp.is_name("naked_identifier", "quoted_identifier") ) alias_info = AliasInfo( name[0].raw if name else "", name[0] if name else None, bool(name), self.selectable, alias_expression[0] if alias_expression else None, None, ) return SelectStatementColumnsAndTables( select_statement=self.selectable, table_aliases=[alias_info], standalone_aliases=[], reference_buffer=[], select_targets=[], col_aliases=[], using_cols=[], )
def select_info(self): """Returns SelectStatementColumnsAndTables on the SELECT.""" result = get_select_statement_info(self.select_statement, self.dialect, early_exit=False) return result
def _eval(self, context: RuleContext) -> Optional[LintResult]: """Look for USING in a join clause.""" segment = context.functional.segment parent_stack = context.functional.parent_stack # We are not concerned with non join clauses if not segment.all(sp.is_type("join_clause")): return None using_anchor = segment.children(sp.is_keyword("using")).first() # If there is no evidence of a USING then we exit if len(using_anchor) == 0: return None anchor = using_anchor.get() description = "Found USING statement. Expected only ON statements." # All returns from here out will be some form of linting error. # we prepare the variable here unfixable_result = LintResult( anchor=anchor, description=description, ) tables_in_join = parent_stack.last().children( sp.is_type("join_clause", "from_expression_element")) # If we have more than 2 tables we won't try to fix join. # TODO: if this is table 2 of 3 it is still fixable if len(tables_in_join) > 2: return unfixable_result parent_select = parent_stack.last(sp.is_type("select_statement")).get() if not parent_select: # pragma: no cover return unfixable_result select_info = get_select_statement_info(parent_select, context.dialect) if not select_info: # pragma: no cover return unfixable_result to_delete, insert_after_anchor = _extract_deletion_sequence_and_anchor( tables_in_join.last()) table_a, table_b = select_info.table_aliases edit_segments = [ KeywordSegment(raw="ON"), WhitespaceSegment(raw=" "), ] + _generate_join_conditions( table_a.ref_str, table_b.ref_str, select_info.using_cols, ) fixes = [ LintFix.create_before( anchor_segment=insert_after_anchor, edit_segments=edit_segments, ), *[LintFix.delete(seg) for seg in to_delete], ] return LintResult( anchor=anchor, description=description, fixes=fixes, )