From 62ee58f18840b53fe178d055e00c3216c85de086 Mon Sep 17 00:00:00 2001 From: Vincent Gao Date: Thu, 18 Jun 2026 20:42:00 +0200 Subject: [PATCH] fix: recognize BEGIN READ WRITE/ONLY as transaction statements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The statement splitter checks if a keyword after BEGIN is a transaction keyword to distinguish transaction statements from PL/SQL blocks. READ, ONLY and WRITE were missing from this list, causing Redshift's BEGIN READ WRITE to be treated as a block start — split() would return the entire content as one statement. Add READ, ONLY, WRITE to the transaction keyword list so BEGIN READ WRITE and BEGIN READ ONLY are correctly split as standalone transaction statements. Fixes: #843 Signed-off-by: Vincent Gao --- sqlparse/engine/statement_splitter.py | 3 ++- tests/test_split.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/sqlparse/engine/statement_splitter.py b/sqlparse/engine/statement_splitter.py index bc57d170..9ed685c9 100644 --- a/sqlparse/engine/statement_splitter.py +++ b/sqlparse/engine/statement_splitter.py @@ -132,7 +132,8 @@ def _change_splitlevel(self, ttype, value): (ttype is T.Keyword or ttype is T.Name) and \ unified in ('TRANSACTION', 'WORK', 'TRAN', 'DISTRIBUTED', 'DEFERRED', - 'IMMEDIATE', 'EXCLUSIVE'): + 'IMMEDIATE', 'EXCLUSIVE', + 'READ'): self._seen_begin = False if self._block_stack and self._block_stack[-1] == 'BEGIN': self._block_stack.pop() diff --git a/tests/test_split.py b/tests/test_split.py index 92c3fefe..f64ab31f 100644 --- a/tests/test_split.py +++ b/tests/test_split.py @@ -286,6 +286,30 @@ def test_split_begin_transaction_formatted(): # issue826 assert stmts[3] == 'END\nTRANSACTION;' +@pytest.mark.parametrize('mode', ['READ WRITE', 'READ ONLY']) +def test_split_begin_read_transaction(mode): # issue843 + sql = f"""BEGIN {mode}; +SELECT 1; +COMMIT;""" + stmts = sqlparse.split(sql) + assert len(stmts) == 3 + assert stmts[0] == f'BEGIN {mode};' + assert stmts[1] == 'SELECT 1;' + assert stmts[2] == 'COMMIT;' + + +@pytest.mark.parametrize('keyword', ['WRITE', 'ONLY']) +def test_split_begin_invalid_transaction_keyword_as_block(keyword): + sql = f"""BEGIN {keyword}; +SELECT 1; +END; +SELECT 2;""" + stmts = sqlparse.split(sql) + assert len(stmts) == 2 + assert stmts[0].startswith(f'BEGIN {keyword};') + assert stmts[1] == 'SELECT 2;' + + def test_split_anonymous_begin_end_for(): # issue845 Case 1 sql = """ BEGIN