def replace_from_clause(sql_spans):
  """Replace fully-specified FROM clause(s) with under-specified FROM clause(s).

  Arguments:
    sql_spans: List of SqlSpan tuples with fully-specified FROM clause.

  Returns:
    List of SqlSpan tuples with under-specified FROM clause.
  """
  replaced_spans = []
  orphaned_tables = _get_tables_without_column_copies(sql_spans)
  # TODO(petershaw): Consider throwing exception if we never find from clause
  # or find more than 1.
  in_from_clause = False
  for span in sql_spans:
    if in_from_clause:
      if span.sql_token and span.sql_token.lower() not in ('join', 'on', '=',
                                                           'inner join'):
        in_from_clause = False
    else:
      if span.sql_token and span.sql_token.lower() == 'from':
        in_from_clause = True
        # Add placeholder and orphoned tables.
        replaced_spans.append(make_sql_span(sql_token=FROM_CLAUSE_PLACEHOLDER))
        for table_name in orphaned_tables:
          replaced_spans.append(make_sql_span(table_name=table_name))
    if not in_from_clause:
      if span.nested_statement:
        # Recursively remove FROM clause from nested expression.
        replaced_spans.append(
            make_sql_span(
                nested_statement=replace_from_clause(span.nested_statement)))
      else:
        replaced_spans.append(span)
  return replaced_spans
Replace fully-specified FROM clause(s) with under-specified FROM clause(s).

  Arguments:
    sql_spans: List of SqlSpan tuples with fully-specified FROM clause.

  Returns:
    List of SqlSpan tuples with under-specified FROM clause.
  
language/xsp/data_preprocessing/abstract_sql.py