Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions sql_metadata/dialect_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,18 @@ def _parse_with_dialect(clean_sql: str, dialect: Any) -> exp.Expression | None:
dialect=dialect,
error_level=sqlglot.ErrorLevel.WARN,
)
except (ParseError, TokenError):
# Re-raise so _try_dialects can report a real syntax error on the
# last dialect (see its except clause).
raise
except Exception:
# WARN mode is supposed to return a best-effort AST instead of
# raising, but sqlglot can still blow up while assembling that tree,
# e.g. an AttributeError on a node whose key is None for input like
# "{ =". Treat any such failure as "this dialect produced nothing"
# so the query is reported as invalid rather than crashing the
# public accessors with a raw sqlglot exception.
return None
finally:
logger.setLevel(old_level)

Expand Down
27 changes: 27 additions & 0 deletions test/test_malformed_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Regression tests for malformed input that used to crash the parser.

sqlglot in best-effort (WARN) mode can build a partial AST and then raise while
assembling it, e.g. an ``AttributeError`` on a node whose key is ``None`` for a
few-character string like ``"{ ="``. That escaped DialectParser and every public
accessor crashed with a raw ``AttributeError`` instead of reporting an invalid
query.
"""

import pytest

from sql_metadata import InvalidQueryDefinition, Parser


@pytest.mark.parametrize("query", ["{ =", "SELECT { =", "x { ="])
def test_bracket_equals_does_not_crash(query):
# query_type / tables validate the AST, so they surface the invalid query
# as InvalidQueryDefinition rather than an AttributeError.
with pytest.raises(InvalidQueryDefinition):
Parser(query).query_type

with pytest.raises(InvalidQueryDefinition):
Parser(query).tables

# The best-effort accessors must simply come back empty, not crash.
assert Parser(query).columns == []
assert Parser(query).columns_dict == {}