def has_equal_ast(
    state,
    incorrect_msg="Check the {ast_path}. {extra}",
    sql=None,
    start=["expression", "subquery", "sql_script"][0],
    exact=None,
):
    """Test whether the student and solution code have identical AST representations

    Args:
        state: State instance describing student and solution code. Can be omitted if used with Ex().
        incorrect_msg: feedback message if student and solution ASTs don't match
        sql  : optional code to use instead of the solution ast that is zoomed in on.
        start: if ``sql`` arg is used, the parser rule to parse the sql code.
               One of 'expression' (the default), 'subquery', or 'sql_script'.
        exact: whether to require an exact match (True), or only that the
               student AST contains the solution AST. If not specified, this
               defaults to ``True`` if ``sql`` is not specified, and to ``False``
               if ``sql`` is specified. You can always specify it manually.

    :Example:

        Example 1 - Suppose the solution code is ::

            SELECT * FROM cities

        and you want to verify whether the `FROM` part is correct: ::

            Ex().check_node('SelectStmt').from_clause().has_equal_ast()

        Example 2 - Suppose the solution code is ::

            SELECT * FROM b WHERE id > 1 AND name = 'filip'

        Then the following SCT makes sure ``id > 1`` was used somewhere in the WHERE clause.::

            Ex().check_node('SelectStmt') \\/
                .check_edge('where_clause') \\/
                .has_equal_ast(sql = 'id > 1')

    """
    ast = state.ast_dispatcher.ast_mod
    sol_ast = state.solution_ast if sql is None else ast.parse(sql, start)

    # if sql is set, exact defaults to False.
    # if sql not set, exact defaults to True.
    if exact is None:
        exact = sql is None

    stu_rep = repr(state.student_ast)
    sol_rep = repr(sol_ast)

    def get_str(ast, code, sql):
        if sql:
            return sql
        if isinstance(ast, str):
            return ast
        try:
            return ast.get_text(code)
        except:
            return None

    sol_str = get_str(state.solution_ast, state.solution_code, sql)
    _msg = incorrect_msg.format(
        ast_path=state.get_ast_path() or "highlighted code",
        extra="The checker expected to find `{}` in there.".format(sol_str)
        if sol_str
        else "Something is missing.",
    )
    if (exact and (sol_rep != stu_rep)) or (not exact and (sol_rep not in stu_rep)):
        state.report(Feedback(_msg))

    return state
Test whether the student and solution code have identical AST representations

    Args:
        state: State instance describing student and solution code. Can be omitted if used with Ex().
        incorrect_msg: feedback message if student and solution ASTs don't match
        sql  : optional code to use instead of the solution ast that is zoomed in on.
        start: if ``sql`` arg is used, the parser rule to parse the sql code.
               One of 'expression' (the default), 'subquery', or 'sql_script'.
        exact: whether to require an exact match (True), or only that the
               student AST contains the solution AST. If not specified, this
               defaults to ``True`` if ``sql`` is not specified, and to ``False``
               if ``sql`` is specified. You can always specify it manually.

    :Example:

        Example 1 - Suppose the solution code is ::

            SELECT * FROM cities

        and you want to verify whether the `FROM` part is correct: ::

            Ex().check_node('SelectStmt').from_clause().has_equal_ast()

        Example 2 - Suppose the solution code is ::

            SELECT * FROM b WHERE id > 1 AND name = 'filip'

        Then the following SCT makes sure ``id > 1`` was used somewhere in the WHERE clause.::

            Ex().check_node('SelectStmt') \\/
                .check_edge('where_clause') \\/
                .has_equal_ast(sql = 'id > 1')