오픈 소스의 버그를 발견한 경험

회사에서 Superset이라는 오픈소스 BI 툴을 사용하던 중에 어이없는 오류를 발견했다.

WITH user_cte AS (
    SELECT name, age
    FROM test
    WHERE age > 20
)
SELECT 
    name as "사람 이름",
    age as "나이",
    COUNT(*) as "something else"
FROM user_cte
GROUP BY name, age;

 

위 쿼리를 사용하면 아래와 같은 오류가 발생한다.

Unexpected error
Custom SQL fields cannot contain sub-queries.

 

하지만 아래 쿼리를 사용하면 오류가 발생하지 않는다.

WITH user_cte AS (
    SELECT name, age
    FROM test
    WHERE age > 20
)
SELECT 
    name as "사람 이름",
    age as "나이",
    COUNT(*) as "테스트 컬럼"
FROM user_cte
GROUP BY name, age;

 

두 쿼리는 같아 보이지만, `COUNT(*)` 함수의 alias 컬럼명이 다르다.

하나는 `영어와 공백`으로 구성되어 있고, 다른 하나는 `한글과 공백`으로 구성되어 있다.

 

또한 두 쿼리 모두 공백을 제거하면 정상적으로 차트가 생성된다.

 

나는 이 상황에 대해 확실하게 버그가 있다고 판단했고, 오픈 소스를 뜯어보기 시작했다.

그 결과 `validate_adhoc_subquery`라는 함수에 문제가 있다는 것을 알았고, 수정을 시도했다.

참고로 이때 회사에서 사용하는 Superset 코드를 뜯어보지 않고, Github에서 최신 버전을 `clone`하여 수정했다.

def validate_adhoc_subquery(
    sql: str,
    database: Database,
    catalog: str | None,
    default_schema: str,
    engine: str,
) -> str:
    """
    Check if adhoc SQL contains sub-queries or nested sub-queries with table.

    If sub-queries are allowed, the adhoc SQL is modified to insert any applicable RLS
    predicates to it.

    :param sql: adhoc sql expression
    :raise SupersetSecurityException if sql contains sub-queries or
    nested sub-queries with table
    """
    parsed_statement = SQLStatement(sql, engine)
    if parsed_statement.has_subquery():
        if not is_feature_enabled("ALLOW_ADHOC_SUBQUERY"):
            raise SupersetSecurityException(
                SupersetError(
                    error_type=SupersetErrorType.ADHOC_SUBQUERY_NOT_ALLOWED_ERROR,
                    message=_("Custom SQL fields cannot contain sub-queries."),
                    level=ErrorLevel.ERROR,
                )
            )

        # enforce RLS rules in any relevant tables
        apply_rls(database, catalog, default_schema, parsed_statement)

    return parsed_statement.format()

 

하지만 코드를 아무리 분석해 봐도 문제가 될 만한 부분을 찾지 못했고, Github Issue를 확인한 뒤 이 페이지를 발견할 수 있었다.

이슈 페이지의 마지막 부분을 살펴보면, `SQLParse`를 `SQLGlot`으로 대체했다는 글이 있었다.

불과 3주 전에 작성되었던 글이다.

(SQLParse를 사용하는 옛날 코드는 더보기 버튼을 클릭)

더보기
def validate_adhoc_subquery(
    sql: str,
    database_id: int,
    engine: str,
    default_schema: str,
) -> str:
    """
    Check if adhoc SQL contains sub-queries or nested sub-queries with table.

    If sub-queries are allowed, the adhoc SQL is modified to insert any applicable RLS
    predicates to it.

    :param sql: adhoc sql expression
    :raise SupersetSecurityException if sql contains sub-queries or
    nested sub-queries with table
    """
    statements = []
    for statement in sqlparse.parse(sql):
        try:
            has_table = has_table_query(str(statement), engine)
        except SupersetParseError:
            has_table = True

        if has_table:
            if not is_feature_enabled("ALLOW_ADHOC_SUBQUERY"):
                raise SupersetSecurityException(
                    SupersetError(
                        error_type=SupersetErrorType.ADHOC_SUBQUERY_NOT_ALLOWED_ERROR,
                        message=_("Custom SQL fields cannot contain sub-queries."),
                        level=ErrorLevel.ERROR,
                    )
                )
            # TODO (betodealmeida): reimplement with sqlglot
            statement = insert_rls_in_predicate(statement, database_id, default_schema)

        statements.append(statement)

    return ";\n".join(str(statement) for statement in statements)

 

즉, 회사에서 사용하는 Superset 버전은 `SQLParse`를 사용하는 3주 전의 버전이었던 것이다.

솔직히 좀 허탈했다.

며칠 동안 열심히 코드 분석을 했지만 물거품이 된 기분이었다.

 

그래도 배운 점도 있다.

오픈 소스에 문제가 있다면 사용하고 있는 버전을 확인하고, Github에서 이 문제가 이미 해결되었는지 확인하자.