represents current state for the "cartesian product" detection feature.
| 741 | |
| 742 | |
| 743 | class FromLinter(collections.namedtuple("FromLinter", ["froms", "edges"])): |
| 744 | """represents current state for the "cartesian product" detection |
| 745 | feature.""" |
| 746 | |
| 747 | def lint(self, start=None): |
| 748 | froms = self.froms |
| 749 | if not froms: |
| 750 | return None, None |
| 751 | |
| 752 | edges = set(self.edges) |
| 753 | the_rest = set(froms) |
| 754 | |
| 755 | if start is not None: |
| 756 | start_with = start |
| 757 | the_rest.remove(start_with) |
| 758 | else: |
| 759 | start_with = the_rest.pop() |
| 760 | |
| 761 | stack = collections.deque([start_with]) |
| 762 | |
| 763 | while stack and the_rest: |
| 764 | node = stack.popleft() |
| 765 | the_rest.discard(node) |
| 766 | |
| 767 | # comparison of nodes in edges here is based on hash equality, as |
| 768 | # there are "annotated" elements that match the non-annotated ones. |
| 769 | # to remove the need for in-python hash() calls, use native |
| 770 | # containment routines (e.g. "node in edge", "edge.index(node)") |
| 771 | to_remove = {edge for edge in edges if node in edge} |
| 772 | |
| 773 | # appendleft the node in each edge that is not |
| 774 | # the one that matched. |
| 775 | stack.extendleft(edge[not edge.index(node)] for edge in to_remove) |
| 776 | edges.difference_update(to_remove) |
| 777 | |
| 778 | # FROMS left over? boom |
| 779 | if the_rest: |
| 780 | return the_rest, start_with |
| 781 | else: |
| 782 | return None, None |
| 783 | |
| 784 | def warn(self, stmt_type="SELECT"): |
| 785 | the_rest, start_with = self.lint() |
| 786 | |
| 787 | # FROMS left over? boom |
| 788 | if the_rest: |
| 789 | froms = the_rest |
| 790 | if froms: |
| 791 | template = ( |
| 792 | "{stmt_type} statement has a cartesian product between " |
| 793 | "FROM element(s) {froms} and " |
| 794 | 'FROM element "{start}". Apply join condition(s) ' |
| 795 | "between each element to resolve." |
| 796 | ) |
| 797 | froms_str = ", ".join( |
| 798 | f'"{self.froms[from_]}"' for from_ in froms |
| 799 | ) |
| 800 | message = template.format( |
no outgoing calls
no test coverage detected