diff --git a/sqlmesh/core/dialect.py b/sqlmesh/core/dialect.py index bcfb300ba1..6dafe52c43 100644 --- a/sqlmesh/core/dialect.py +++ b/sqlmesh/core/dialect.py @@ -1124,7 +1124,14 @@ def text_diff( return "\n".join(unified_diff(a_sql, b_sql)) -WS_OR_COMMENT = r"(?:\s|--[^\n]*\n|/\*.*?\*/)" +# Each '*/' closes exactly one '/*', so a run of comments matches in exactly one way. A lazy +# '/\*.*?\*/' under DOTALL can stretch across several comments, and a failed search then +# backtracks through every grouping of them. SQLGlot reads block comments as nested, so this +# matches nesting up to three levels deep; a regex cannot match arbitrary depth. +BLOCK_COMMENT = r"/\*(?:[^*]|\*(?!/))*\*/" +for _ in range(2): + BLOCK_COMMENT = rf"/\*(?:[^*/]|\*(?!/)|/(?!\*)|{BLOCK_COMMENT})*\*/" +WS_OR_COMMENT = rf"(?:\s|--[^\n]*\n|{BLOCK_COMMENT})" HEADER = r"\b(?:model|audit)\b(?=\s*\()" KEY_BOUNDARY = r"(?:\(|,)" # key is preceded by either '(' or ',' DIALECT_VALUE = r"['\"]?(?P[a-z][a-z0-9]*)['\"]?" diff --git a/tests/core/test_model.py b/tests/core/test_model.py index c5d1a71bee..34bb5d1d1d 100644 --- a/tests/core/test_model.py +++ b/tests/core/test_model.py @@ -3151,6 +3151,26 @@ def assert_match(test_sql: str, expected_value: t.Optional[str] = "duckdb"): """ ) + # a run of block comments before the dialect key + comments = "\n".join(f"/* note {i} */" for i in range(40)) + assert_match(make_test_sql(f"tag my_tag, {comments}\ndialect duckdb")) + + # a run of block comments and no dialect key: the search fails without backtracking + # through every grouping of the comments + test_sql = make_test_sql(f"tag my_tag, {comments}\ncron '@daily'") + matches = list(d.DIALECT_PATTERN.finditer(test_sql)) + assert not matches + + # nested block comments before the dialect key + assert_match( + make_test_sql( + """ + tag my_tag, /* outer /* inner /* innermost */ */ still outer */ + dialect duckdb + """ + ) + ) + # full parse sql = """ MODEL (