Skip to content
Closed
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
35 changes: 31 additions & 4 deletions sqlparse/engine/statement_splitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ def _reset(self):
self._parenthesis_level = 0
self._unconfirmed_start = None
self._is_create = False
self._is_create_procedure = False
self._procedure_body_started = False
self._seen_begin = False

self.consume_ws = False
Expand Down Expand Up @@ -109,6 +111,13 @@ def _change_splitlevel(self, ttype, value):
self._is_create = True
return 0

# Oracle-style CREATE PROCEDURE statements can contain declarations
# terminated by semicolons between AS/IS and the BEGIN body. Keep
# those semicolons inside the CREATE statement until its outer END.
if self._is_create and unified == 'PROCEDURE':
self._is_create_procedure = True
return 0

# Handle DECLARE block start (only for CREATE statements)
if unified == 'DECLARE' and self._is_create and not self._block_stack:
self._block_stack.append('DECLARE')
Expand All @@ -117,6 +126,8 @@ def _change_splitlevel(self, ttype, value):
# Handle BEGIN block start
if unified == 'BEGIN':
self._seen_begin = True
if self._is_create_procedure:
self._procedure_body_started = True
# Transition DECLARE to BEGIN if present
if self._block_stack and self._block_stack[-1] == 'DECLARE':
self._block_stack.pop()
Expand Down Expand Up @@ -145,8 +156,19 @@ def _change_splitlevel(self, ttype, value):
if res is not None:
return res

# Handle closing keywords
return self._handle_closing_keyword(unified)
# A top-level END closes a CREATE PROCEDURE body. Clear the routine
# state before its following semicolon so the statement can be yielded.
procedure_end = (
unified == 'END'
and self._is_create_procedure
and self._procedure_body_started
and self._block_stack == ['BEGIN']
)
result = self._handle_closing_keyword(unified)
if procedure_end:
self._is_create_procedure = False
self._procedure_body_started = False
return result

def process(self, stream):
"""Process the stream"""
Expand Down Expand Up @@ -179,8 +201,13 @@ def process(self, stream):
# standalone BEGIN; as a transaction statement
if ttype is T.Punctuation and value == ';':
self._seen_begin = False
# Split on semicolon if not inside a BEGIN...END block
if self.level <= 0 and 'BEGIN' not in self._block_stack:
# Split on semicolon if not inside a BEGIN...END block or the
# declaration section of a CREATE PROCEDURE.
if (
self.level <= 0
and 'BEGIN' not in self._block_stack
and not self._is_create_procedure
):
self.consume_ws = True
elif ttype is T.Keyword and value.split()[0] == 'GO':
self.consume_ws = True
Expand Down
33 changes: 33 additions & 0 deletions tests/test_split_issue692.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import sqlparse


def test_split_oracle_procedure_with_declarations():
sql = """CREATE PROCEDURE remove_emp (employee_id NUMBER) AS
tot_emps NUMBER;
BEGIN
DELETE FROM employees
WHERE employees.employee_id = remove_emp.employee_id;
tot_emps := tot_emps - 1;
END;"""

statements = sqlparse.split(sql)

assert len(statements) == 1
assert statements[0] == sql


def test_split_oracle_procedure_then_statement():
sql = """CREATE OR REPLACE PROCEDURE update_counter AS
counter NUMBER;
label VARCHAR2(20);
BEGIN
counter := 1;
label := 'ready';
END;
SELECT 42;"""

statements = sqlparse.split(sql)

assert len(statements) == 2
assert statements[0].endswith("END;")
assert statements[1] == "SELECT 42;"