diff --git a/CHANGELOG.md b/CHANGELOG.md index b3832ab2b..e027df9ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## 0.11.2dev +* [Fix] Handle empty SQL statements gracefully (#1066) + ## 0.11.1 (2025-03-25) * [Fix] No longer showing the Slack link in error messages diff --git a/src/sql/run/run.py b/src/sql/run/run.py index a1e34aa7d..8513bf135 100644 --- a/src/sql/run/run.py +++ b/src/sql/run/run.py @@ -29,18 +29,30 @@ def run_statements(conn, sql, config, parameters=None): .. literalinclude:: ../../examples/run_statements.py """ - if not sql.strip(): + # First, try to extract the usable SQL statements without comments + statements = list( + filter( + None, + map( + lambda stmt: sqlparse.format(stmt, strip_comments=True), + sqlparse.split(sql), + ), + ) + ) + + # Handle the empty SQL case + if not sql.strip() or not statements: return "Connected: %s" % conn.name - for statement in sqlparse.split(sql): - # strip all comments from sql - statement = sqlparse.format(statement, strip_comments=True) - # trailing comment after semicolon can be confused as its own statement, - # so we ignore it here. - if not statement: + # Ensure we have some return value. This maintains type checkers happy + result = None + statement = None + + for statement in statements: + if not statement.strip(): continue - first_word = sql.strip().split()[0].lower() + first_word = statement.strip().split()[0].lower() if first_word == "begin": raise exceptions.RuntimeError("JupySQL does not support transactions") diff --git a/src/tests/test_magic.py b/src/tests/test_magic.py index c0ee3c357..b5865c1fe 100644 --- a/src/tests/test_magic.py +++ b/src/tests/test_magic.py @@ -1221,7 +1221,7 @@ def test_error_on_invalid_connection_string_with_possible_typo(ip_empty, clean_c """ # noqa invalid_connection_string_duckdb_bottom = f""" -Perhaps you meant to use the 'duckdb' db +Perhaps you meant to use the 'duckdb' db To find more information regarding connection: https://jupysql.ploomber.io/en/latest/integrations/duckdb.html To fix it: @@ -1783,7 +1783,7 @@ def test_error_when_using_section_argument_but_values_are_invalid(ip_empty, tmp_ with pytest.raises(UsageError) as excinfo: ip_empty.run_cell("%sql --section section") - message = "Could not parse SQLAlchemy URL from string 'not-a-driver://'" + message = "Could not parse SQLAlchemy URL from given URL string" assert message in str(excinfo.value) diff --git a/src/tests/test_run.py b/src/tests/test_run.py index 6557c1540..7f25cc25e 100644 --- a/src/tests/test_run.py +++ b/src/tests/test_run.py @@ -108,6 +108,13 @@ def test_sql_is_empty(mock_conns): assert run_statements(mock_conns, " ", Config) == "Connected: %s" % mock_conns.name +def test_sql_comment_only(mock_conns): + assert ( + run_statements(mock_conns, "-- this is a comment", Config) + == "Connected: %s" % mock_conns.name + ) + + @pytest.mark.parametrize( "connection", [