represents current state for the "cartesian product" detection feature.
| 707 | |
| 708 | |
| 709 | class FromLinter(collections.namedtuple("FromLinter", ["froms", "edges"])): |
| 710 | """represents current state for the "cartesian product" detection |
| 711 | feature.""" |
| 712 | |
| 713 | def lint(self, start=None): |
| 714 | froms = self.froms |
| 715 | if not froms: |
| 716 | return None, None |
| 717 | |
| 718 | edges = set(self.edges) |
| 719 | the_rest = set(froms) |
| 720 | |
| 721 | if start is not None: |
| 722 | start_with = start |
| 723 | the_rest.remove(start_with) |
| 724 | else: |
| 725 | start_with = the_rest.pop() |
| 726 | |
| 727 | stack = collections.deque([start_with]) |
| 728 | |
| 729 | while stack and the_rest: |
| 730 | node = stack.popleft() |
| 731 | the_rest.discard(node) |
| 732 | |
| 733 | # comparison of nodes in edges here is based on hash equality, as |
| 734 | # there are "annotated" elements that match the non-annotated ones. |
| 735 | # to remove the need for in-python hash() calls, use native |
| 736 | # containment routines (e.g. "node in edge", "edge.index(node)") |
| 737 | to_remove = {edge for edge in edges if node in edge} |
| 738 | |
| 739 | # appendleft the node in each edge that is not |
| 740 | # the one that matched. |
| 741 | stack.extendleft(edge[not edge.index(node)] for edge in to_remove) |
| 742 | edges.difference_update(to_remove) |
| 743 | |
| 744 | # FROMS left over? boom |
| 745 | if the_rest: |
| 746 | return the_rest, start_with |
| 747 | else: |
| 748 | return None, None |
| 749 | |
| 750 | def warn(self, stmt_type="SELECT"): |
| 751 | the_rest, start_with = self.lint() |
| 752 | |
| 753 | # FROMS left over? boom |
| 754 | if the_rest: |
| 755 | froms = the_rest |
| 756 | if froms: |
| 757 | template = ( |
| 758 | "{stmt_type} statement has a cartesian product between " |
| 759 | "FROM element(s) {froms} and " |
| 760 | 'FROM element "{start}". Apply join condition(s) ' |
| 761 | "between each element to resolve." |
| 762 | ) |
| 763 | froms_str = ", ".join( |
| 764 | f'"{self.froms[from_]}"' for from_ in froms |
| 765 | ) |
| 766 | message = template.format( |
no outgoing calls
no test coverage detected