| 126 | |
| 127 | |
| 128 | def extract_from_part(parsed: TokenList, stop_at_punctuation: bool = True) -> Generator[Any, None, None]: |
| 129 | tbl_prefix_seen = False |
| 130 | for item in parsed.tokens: |
| 131 | if tbl_prefix_seen: |
| 132 | if is_subselect(item): |
| 133 | yield from extract_from_part(item, stop_at_punctuation) |
| 134 | elif stop_at_punctuation and item.ttype is Punctuation: |
| 135 | return None |
| 136 | # Multiple JOINs in the same query won't work properly since |
| 137 | # "ON" is a keyword and will trigger the next elif condition. |
| 138 | # So instead of stooping the loop when finding an "ON" skip it |
| 139 | # eg: 'SELECT * FROM abc JOIN def ON abc.id = def.abc_id JOIN ghi' |
| 140 | elif item.ttype is Keyword and item.value.upper() == "ON": |
| 141 | tbl_prefix_seen = False |
| 142 | continue |
| 143 | # An incomplete nested select won't be recognized correctly as a |
| 144 | # sub-select. eg: 'SELECT * FROM (SELECT id FROM user'. This causes |
| 145 | # the second FROM to trigger this elif condition resulting in a |
| 146 | # StopIteration. So we need to ignore the keyword if the keyword |
| 147 | # FROM. |
| 148 | # Also 'SELECT * FROM abc JOIN def' will trigger this elif |
| 149 | # condition. So we need to ignore the keyword JOIN and its variants |
| 150 | # INNER JOIN, FULL OUTER JOIN, etc. |
| 151 | elif item.ttype is Keyword and (not item.value.upper() == "FROM") and (not item.value.upper().endswith("JOIN")): |
| 152 | return None |
| 153 | else: |
| 154 | yield item |
| 155 | elif (item.ttype is Keyword or item.ttype is Keyword.DML) and item.value.upper() in ( |
| 156 | "COPY", |
| 157 | "FROM", |
| 158 | "INTO", |
| 159 | "UPDATE", |
| 160 | "TABLE", |
| 161 | "JOIN", |
| 162 | ): |
| 163 | tbl_prefix_seen = True |
| 164 | # 'SELECT a, FROM abc' will detect FROM as part of the column list. |
| 165 | # So this check here is necessary. |
| 166 | elif isinstance(item, IdentifierList): |
| 167 | for identifier in item.get_identifiers(): |
| 168 | if identifier.ttype is Keyword and identifier.value.upper() == "FROM": |
| 169 | tbl_prefix_seen = True |
| 170 | break |
| 171 | |
| 172 | |
| 173 | def extract_table_identifiers(token_stream: Generator[Any, None, None]) -> Generator[tuple[str | None, str, str], None, None]: |