From c9f862abbc1db0c8e236b3a021a4cae1996d1938 Mon Sep 17 00:00:00 2001 From: Peter Adams <18162810+Maxteabag@users.noreply.github.com> Date: Tue, 14 Jul 2026 17:26:23 +0200 Subject: [PATCH] Prevent trailing comments from blocking single-query execution The process worker counted raw SQL fragments before removing comment-only fragments, so a comment after a semicolon was rejected as a second statement. Reuse the existing comment classifier when enforcing the worker's single-statement boundary. Constraint: Process workers must continue rejecting multiple executable statements Rejected: Change SQL splitting semantics | broader behavior change than the worker validation bug requires Confidence: high Scope-risk: narrow Directive: Keep worker statement validation aligned with other executable-statement paths Tested: 48 focused process-worker and multi-statement tests; Ruff; mypy Not-tested: Standalone trailing block comments Related: #254 --- .../process_worker/app/process_worker.py | 9 ++- ...est_process_worker_statement_validation.py | 68 +++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test_process_worker_statement_validation.py diff --git a/sqlit/domains/process_worker/app/process_worker.py b/sqlit/domains/process_worker/app/process_worker.py index 31008e7c..ee3e4cd8 100644 --- a/sqlit/domains/process_worker/app/process_worker.py +++ b/sqlit/domains/process_worker/app/process_worker.py @@ -140,7 +140,14 @@ def _start_query(self, message: dict[str, Any]) -> None: ) return - if len(split_statements(query)) > 1: + from sqlit.domains.query.editing.comments import is_comment_only_statement + + executable_statements = [ + statement + for statement in split_statements(query) + if not is_comment_only_statement(statement) + ] + if len(executable_statements) > 1: self.send( { "type": "error", diff --git a/tests/unit/test_process_worker_statement_validation.py b/tests/unit/test_process_worker_statement_validation.py new file mode 100644 index 00000000..ef8cba1b --- /dev/null +++ b/tests/unit/test_process_worker_statement_validation.py @@ -0,0 +1,68 @@ +"""Regression tests for process-worker statement validation.""" + +from __future__ import annotations + +from unittest.mock import MagicMock, patch + +from sqlit.domains.process_worker.app.process_worker import _WorkerState + + +def test_single_statement_with_trailing_comment_reaches_provider_lookup() -> None: + """A trailing comment must not turn one query into two statements.""" + connection = MagicMock() + state = _WorkerState(conn=connection) + + with patch.object(state, "_get_provider", return_value=None) as get_provider: + state._start_query( + { + "id": 254, + "query": "SELECT 1;\n-- trailing comment", + "db_type": "sqlite", + "config": { + "name": "issue-254", + "db_type": "sqlite", + "file_path": ":memory:", + }, + } + ) + + rejection = next( + ( + call.args[0] + for call in connection.send.call_args_list + if call.args[0].get("message") + == "Multi-statement queries are not supported in the process worker." + ), + None, + ) + assert rejection is None, rejection["message"] if rejection else "" + get_provider.assert_called_once_with("sqlite") + + +def test_multiple_executable_statements_are_rejected() -> None: + """The comment fix must not allow genuine multi-statement queries.""" + connection = MagicMock() + state = _WorkerState(conn=connection) + + with patch.object(state, "_get_provider") as get_provider: + state._start_query( + { + "id": 255, + "query": "SELECT 1; SELECT 2", + "db_type": "sqlite", + "config": { + "name": "multi-statement", + "db_type": "sqlite", + "file_path": ":memory:", + }, + } + ) + + get_provider.assert_not_called() + connection.send.assert_called_once_with( + { + "type": "error", + "id": 255, + "message": "Multi-statement queries are not supported in the process worker.", + } + )