Skip to content

Commit e38204b

Browse files
committed
New keyword "Split SQL Script"
1 parent bfd0745 commit e38204b

File tree

3 files changed

+101
-71
lines changed

3 files changed

+101
-71
lines changed

src/DatabaseLibrary/query.py

Lines changed: 84 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -293,84 +293,98 @@ def execute_sql_script(
293293
| Execute SQL Script | insert_data_in_person_table.sql | split=False |
294294
"""
295295
db_connection = self.connection_store.get_connection(alias)
296-
with open(script_path, encoding="UTF-8") as sql_file:
297-
cur = None
298-
try:
299-
cur = db_connection.client.cursor()
300-
if not split:
296+
cur = None
297+
try:
298+
cur = db_connection.client.cursor()
299+
if not split:
300+
with open(script_path, encoding="UTF-8") as sql_file:
301301
logger.info("Statements splitting disabled - pass entire script content to the database module")
302302
self._execute_sql(
303303
cur,
304304
sql_file.read(),
305305
omit_trailing_semicolon=db_connection.omit_trailing_semicolon,
306306
)
307-
else:
308-
logger.info("Splitting script file into statements...")
309-
statements_to_execute = []
310-
current_statement = ""
311-
inside_statements_group = False
312-
proc_start_pattern = re.compile("create( or replace)? (procedure|function){1}( )?")
307+
else:
308+
statements_to_execute = self.split_sql_script(script_path)
309+
for statement in statements_to_execute:
313310
proc_end_pattern = re.compile("end(?!( if;| loop;| case;| while;| repeat;)).*;()?")
314-
for line in sql_file:
315-
line = line.strip()
316-
if line.startswith("#") or line.startswith("--") or line == "/":
317-
continue
318-
319-
# check if the line matches the creating procedure regexp pattern
320-
if proc_start_pattern.match(line.lower()):
321-
inside_statements_group = True
322-
elif line.lower().startswith("begin"):
323-
inside_statements_group = True
324-
325-
# semicolons inside the line? use them to separate statements
326-
# ... but not if they are inside a begin/end block (aka. statements group)
327-
sqlFragments = line.split(";")
328-
# no semicolons
329-
if len(sqlFragments) == 1:
330-
current_statement += line + " "
331-
continue
311+
line_ends_with_proc_end = re.compile(r"(\s|;)" + proc_end_pattern.pattern + "$")
312+
omit_semicolon = not line_ends_with_proc_end.search(statement.lower())
313+
self._execute_sql(cur, statement, omit_semicolon)
314+
self._commit_if_needed(db_connection, no_transaction)
315+
except Exception as e:
316+
self._rollback_and_raise(db_connection, no_transaction, e)
317+
318+
def split_sql_script(
319+
self,
320+
script_path: str,
321+
):
322+
"""
323+
Splits the content of the SQL script file loaded from `script_path` into individual SQL commands
324+
and returns them as a list of strings.
325+
SQL commands are expected to be delimited by a semicolon (';').
326+
"""
327+
with open(script_path, encoding="UTF-8") as sql_file:
328+
logger.info("Splitting script file into statements...")
329+
statements_to_execute = []
330+
current_statement = ""
331+
inside_statements_group = False
332+
proc_start_pattern = re.compile("create( or replace)? (procedure|function){1}( )?")
333+
proc_end_pattern = re.compile("end(?!( if;| loop;| case;| while;| repeat;)).*;()?")
334+
for line in sql_file:
335+
line = line.strip()
336+
if line.startswith("#") or line.startswith("--") or line == "/":
337+
continue
338+
339+
# check if the line matches the creating procedure regexp pattern
340+
if proc_start_pattern.match(line.lower()):
341+
inside_statements_group = True
342+
elif line.lower().startswith("begin"):
343+
inside_statements_group = True
344+
345+
# semicolons inside the line? use them to separate statements
346+
# ... but not if they are inside a begin/end block (aka. statements group)
347+
sqlFragments = line.split(";")
348+
# no semicolons
349+
if len(sqlFragments) == 1:
350+
current_statement += line + " "
351+
continue
352+
quotes = 0
353+
# "select * from person;" -> ["select..", ""]
354+
for sqlFragment in sqlFragments:
355+
if len(sqlFragment.strip()) == 0:
356+
continue
357+
358+
if inside_statements_group:
359+
# if statements inside a begin/end block have semicolns,
360+
# they must persist - even with oracle
361+
sqlFragment += "; "
362+
363+
if proc_end_pattern.match(sqlFragment.lower()):
364+
inside_statements_group = False
365+
elif proc_start_pattern.match(sqlFragment.lower()):
366+
inside_statements_group = True
367+
elif sqlFragment.lower().startswith("begin"):
368+
inside_statements_group = True
369+
370+
# check if the semicolon is a part of the value (quoted string)
371+
quotes += sqlFragment.count("'")
372+
quotes -= sqlFragment.count("\\'")
373+
inside_quoted_string = quotes % 2 != 0
374+
if inside_quoted_string:
375+
sqlFragment += ";" # restore the semicolon
376+
377+
current_statement += sqlFragment
378+
if not inside_statements_group and not inside_quoted_string:
379+
statements_to_execute.append(current_statement.strip())
380+
current_statement = ""
332381
quotes = 0
333-
# "select * from person;" -> ["select..", ""]
334-
for sqlFragment in sqlFragments:
335-
if len(sqlFragment.strip()) == 0:
336-
continue
337-
338-
if inside_statements_group:
339-
# if statements inside a begin/end block have semicolns,
340-
# they must persist - even with oracle
341-
sqlFragment += "; "
342-
343-
if proc_end_pattern.match(sqlFragment.lower()):
344-
inside_statements_group = False
345-
elif proc_start_pattern.match(sqlFragment.lower()):
346-
inside_statements_group = True
347-
elif sqlFragment.lower().startswith("begin"):
348-
inside_statements_group = True
349-
350-
# check if the semicolon is a part of the value (quoted string)
351-
quotes += sqlFragment.count("'")
352-
quotes -= sqlFragment.count("\\'")
353-
inside_quoted_string = quotes % 2 != 0
354-
if inside_quoted_string:
355-
sqlFragment += ";" # restore the semicolon
356-
357-
current_statement += sqlFragment
358-
if not inside_statements_group and not inside_quoted_string:
359-
statements_to_execute.append(current_statement.strip())
360-
current_statement = ""
361-
quotes = 0
362-
363-
current_statement = current_statement.strip()
364-
if len(current_statement) != 0:
365-
statements_to_execute.append(current_statement)
366-
367-
for statement in statements_to_execute:
368-
line_ends_with_proc_end = re.compile(r"(\s|;)" + proc_end_pattern.pattern + "$")
369-
omit_semicolon = not line_ends_with_proc_end.search(statement.lower())
370-
self._execute_sql(cur, statement, omit_semicolon)
371-
self._commit_if_needed(db_connection, no_transaction)
372-
except Exception as e:
373-
self._rollback_and_raise(db_connection, no_transaction, e)
382+
383+
current_statement = current_statement.strip()
384+
if len(current_statement) != 0:
385+
statements_to_execute.append(current_statement)
386+
387+
return statements_to_execute
374388

375389
@renamed_args(
376390
mapping={
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
SELECT * FROM person;
2+
SELECT * FROM person WHERE id=1;

test/tests/common_tests/script_files.robot

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ Suite Teardown Disconnect From Database
66
Test Setup Create Person Table
77
Test Teardown Drop Tables Person And Foobar
88

9+
*** Variables ***
10+
${Script files dir} ${CURDIR}/../../resources/script_file_tests
11+
912

1013
*** Test Cases ***
1114
Semicolons As Statement Separators In One Line
@@ -35,9 +38,20 @@ Semicolons And Quotes In Values
3538
Should Be Equal As Strings ${results}[0] (5, 'Miles', "O'Brian")
3639
Should Be Equal As Strings ${results}[1] (6, 'Keiko', "O'Brian")
3740

41+
Split Script Into Statements
42+
Insert Data In Person Table Using SQL Script
43+
@{Expected commands}= Create List
44+
... SELECT * FROM person
45+
... SELECT * FROM person WHERE id=1
46+
${extracted commands}= Split Sql Script ${Script files dir}/split_commands.sql
47+
Lists Should Be Equal ${Expected commands} ${extracted commands}
48+
FOR ${command} IN @{extracted commands}
49+
${results}= Query ${command}
50+
END
51+
52+
3853

3954
*** Keywords ***
4055
Run SQL Script File
4156
[Arguments] ${File Name}
42-
${Script files dir}= Set Variable ${CURDIR}/../../resources/script_file_tests
4357
Execute Sql Script ${Script files dir}/${File Name}.sql

0 commit comments

Comments
 (0)