회사에서 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에서 이 문제가 이미 해결되었는지 확인하자.