From 13e659b715abd89dad7253c42822179c88ed439f Mon Sep 17 00:00:00 2001 From: Rami Abdelrazzaq Date: Mon, 14 Sep 2026 09:19:17 -0500 Subject: [PATCH 1/5] feat: expose configurable grouping token limit --- sqlparse/__init__.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/sqlparse/__init__.py b/sqlparse/__init__.py index e62d9785..cd446004 100644 --- a/sqlparse/__init__.py +++ b/sqlparse/__init__.py @@ -16,10 +16,30 @@ from sqlparse import tokens from sqlparse import filters from sqlparse import formatter +from sqlparse.engine import grouping as _grouping __version__ = "0.5.6.dev0" -__all__ = ["engine", "filters", "formatter", "sql", "tokens", "cli"] +__all__ = [ + "engine", + "filters", + "formatter", + "sql", + "tokens", + "cli", + "set_max_grouping_tokens", +] + + +def set_max_grouping_tokens(limit: Optional[int]) -> None: + """Set the maximum token count accepted by the grouping stage. + + Pass ``None`` to disable the token-count limit. Positive integers set a + process-wide limit for subsequent parsing and formatting operations. + """ + if limit is not None and (isinstance(limit, bool) or not isinstance(limit, int) or limit < 1): + raise ValueError("Grouping token limit must be a positive integer or None") + _grouping.MAX_GROUPING_TOKENS = limit def parse( From 36e39246626e22afb70682947242afbdd02430ba Mon Sep 17 00:00:00 2001 From: Rami Abdelrazzaq Date: Mon, 14 Sep 2026 09:19:29 -0500 Subject: [PATCH 2/5] test: cover configurable grouping token limit --- tests/test_grouping_config.py | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/test_grouping_config.py diff --git a/tests/test_grouping_config.py b/tests/test_grouping_config.py new file mode 100644 index 00000000..aa4bdc83 --- /dev/null +++ b/tests/test_grouping_config.py @@ -0,0 +1,36 @@ +import pytest + +import sqlparse +from sqlparse.engine import grouping +from sqlparse.exceptions import SQLParseError + + +@pytest.fixture(autouse=True) +def restore_grouping_token_limit(): + original = grouping.MAX_GROUPING_TOKENS + try: + yield + finally: + grouping.MAX_GROUPING_TOKENS = original + + +def test_set_max_grouping_tokens_changes_parser_limit(): + sqlparse.set_max_grouping_tokens(1) + + with pytest.raises(SQLParseError, match="Maximum number of tokens exceeded"): + sqlparse.parse("select value from example") + + +def test_set_max_grouping_tokens_none_disables_limit(): + sqlparse.set_max_grouping_tokens(None) + + assert len(sqlparse.parse("select value from example")) == 1 + + +@pytest.mark.parametrize("limit", (0, -1, True, 1.5, "100")) +def test_set_max_grouping_tokens_rejects_invalid_values(limit): + with pytest.raises( + ValueError, + match="Grouping token limit must be a positive integer or None", + ): + sqlparse.set_max_grouping_tokens(limit) From 657b695154b6d7567fc158901fd29ac06904165b Mon Sep 17 00:00:00 2001 From: Rami Abdelrazzaq Date: Mon, 14 Sep 2026 09:19:52 -0500 Subject: [PATCH 3/5] style: format grouping limit setter --- sqlparse/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sqlparse/__init__.py b/sqlparse/__init__.py index cd446004..3e241446 100644 --- a/sqlparse/__init__.py +++ b/sqlparse/__init__.py @@ -37,7 +37,9 @@ def set_max_grouping_tokens(limit: Optional[int]) -> None: Pass ``None`` to disable the token-count limit. Positive integers set a process-wide limit for subsequent parsing and formatting operations. """ - if limit is not None and (isinstance(limit, bool) or not isinstance(limit, int) or limit < 1): + if limit is not None and ( + isinstance(limit, bool) or not isinstance(limit, int) or limit < 1 + ): raise ValueError("Grouping token limit must be a positive integer or None") _grouping.MAX_GROUPING_TOKENS = limit From 2b1c11d82abc365893600894010594b84e1bb391 Mon Sep 17 00:00:00 2001 From: Rami Abdelrazzaq Date: Mon, 14 Sep 2026 10:26:52 -0500 Subject: [PATCH 4/5] docs(api): warn about disabling grouping token guard --- sqlparse/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sqlparse/__init__.py b/sqlparse/__init__.py index 3e241446..ae778048 100644 --- a/sqlparse/__init__.py +++ b/sqlparse/__init__.py @@ -36,6 +36,8 @@ def set_max_grouping_tokens(limit: Optional[int]) -> None: Pass ``None`` to disable the token-count limit. Positive integers set a process-wide limit for subsequent parsing and formatting operations. + Disabling or increasing this limit is not recommended for SQL from + untrusted sources. """ if limit is not None and ( isinstance(limit, bool) or not isinstance(limit, int) or limit < 1 From 284dd67ecae856fb9a6b3eff98b194074f2b35e5 Mon Sep 17 00:00:00 2001 From: Rami Abdelrazzaq Date: Mon, 14 Sep 2026 10:27:00 -0500 Subject: [PATCH 5/5] test(api): cover grouping limit in formatter --- tests/test_grouping_config_format.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/test_grouping_config_format.py diff --git a/tests/test_grouping_config_format.py b/tests/test_grouping_config_format.py new file mode 100644 index 00000000..d33dfc76 --- /dev/null +++ b/tests/test_grouping_config_format.py @@ -0,0 +1,15 @@ +import pytest + +import sqlparse +from sqlparse.engine import grouping +from sqlparse.exceptions import SQLParseError + + +def test_set_max_grouping_tokens_applies_to_formatting(): + original = grouping.MAX_GROUPING_TOKENS + try: + sqlparse.set_max_grouping_tokens(1) + with pytest.raises(SQLParseError, match="Maximum number of tokens exceeded"): + sqlparse.format("select value from example", reindent=True) + finally: + grouping.MAX_GROUPING_TOKENS = original