Skip to content
Merged
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
29 changes: 28 additions & 1 deletion aw_query/query2.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,33 @@ def get_return(namespace):
return namespace["RETURN"]


def _split_query_statements(query: str) -> List[str]:
"""Split query into statements on semicolons, ignoring semicolons inside string literals."""
statements = []
current = ""
in_single_quote = False
in_double_quote = False
prev_char = None

for char in query:
if char == "'" and prev_char != "\\" and not in_double_quote:
in_single_quote = not in_single_quote
current += char
elif char == '"' and prev_char != "\\" and not in_single_quote:
in_double_quote = not in_double_quote
current += char
elif char == ";" and not in_single_quote and not in_double_quote:
statements.append(current)
current = ""
else:
current += char
prev_char = char

if current:
statements.append(current)
return statements


def query(
name: str, query: str, starttime: datetime, endtime: datetime, datastore: Datastore
) -> Any:
Expand All @@ -409,7 +436,7 @@ def query(
namespace["STARTTIME"] = starttime.isoformat()
namespace["ENDTIME"] = endtime.isoformat()

query_stmts = query.split(";")
query_stmts = _split_query_statements(query)
for statement in query_stmts:
statement = statement.strip()
if statement:
Expand Down
36 changes: 36 additions & 0 deletions tests/test_query2.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
QString,
QVariable,
_parse_token,
_split_query_statements,
)

from .utils import param_datastore_objects
Expand Down Expand Up @@ -229,6 +230,41 @@ def test_query2_return_value():
result = query(qname, example_query, starttime, endtime, ds)


def test_query2_semicolon_in_string():
"""Test that semicolons inside string literals don't break query parsing (issue #145)."""
ds = mock_ds
qname = "asd"
starttime = iso8601.parse_date("1970-01-01")
endtime = iso8601.parse_date("1970-01-02")

# Semicolon in double-quoted string
example_query = 'RETURN = "hello;world"'
result = query(qname, example_query, starttime, endtime, ds)
assert result == "hello;world"

# Semicolon in single-quoted string
example_query = "RETURN = 'hello;world'"
result = query(qname, example_query, starttime, endtime, ds)
assert result == "hello;world"

# Multiple statements where one value contains a semicolon in a string
example_query = 'a = "foo;bar"; RETURN = a'
result = query(qname, example_query, starttime, endtime, ds)
assert result == "foo;bar"


def test_split_query_statements():
"""Unit test for _split_query_statements."""
# Basic split
assert _split_query_statements("a=1;b=2") == ["a=1", "b=2"]
# Semicolon inside double quotes should NOT split
assert _split_query_statements('a="x;y";b=2') == ['a="x;y"', "b=2"]
# Semicolon inside single quotes should NOT split
assert _split_query_statements("a='x;y';b=2") == ["a='x;y'", "b=2"]
# Trailing semicolon — empty trailing part is omitted
assert _split_query_statements("a=1;") == ["a=1"]


def test_query2_multiline():
ds = mock_ds
qname = "asd"
Expand Down
Loading