def _parse_sql_data_type(type_str: str) -> str:
    """
    Convert a full SQL type string (e.g. "MAP<STRING, ARRAY<STRING>>")
    into a Python type annotation string (e.g. "dict[str, list[str]]").
    """
    type_str = type_str.strip()

    if "<" not in type_str:
        if mapped := SQL_TYPE_TO_PYTHON_TYPE_MAPPING.get(type_str.upper()):
            return mapped[0].__name__ if isinstance(mapped, tuple) else mapped.__name__
        else:
            return type_str.lower()

    outer, inner = type_str.split("<", 1)
    outer = outer.strip().upper()
    inner = inner.rsplit(">", 1)[0].strip()

    parts = _split_generic_types(inner)

    # recurse
    parsed_parts = [_parse_sql_data_type(part) for part in parts]

    # NB: For Python tuples, we map both tuple and list to the SQL ARRAY type.
    # Since we can't disambiguate between them in the SQL type string, we assume that
    # the collection container type is list for simplicity's sake, which is the first
    # mapping entry in SQL_TYPE_TO_PYTHON_TYPE_MAPPING for ARRAY types.
    if outer == "ARRAY":
        return f"list[{parsed_parts[0]}]"
    elif outer == "MAP":
        return f"dict[{parsed_parts[0]}, {parsed_parts[1]}]"
    elif outer == "STRUCT":
        return "dict"
    else:
        return f"{outer.lower()}[{', '.join(parsed_parts)}]"

    Convert a full SQL type string (e.g. "MAP<STRING, ARRAY<STRING>>")
    into a Python type annotation string (e.g. "dict[str, list[str]]").
    
ai/core/src/unitycatalog/ai/core/utils/callable_utils.py