diff --git a/CHANGELOG.md b/CHANGELOG.md index 6baf1ba9c..4d7271ec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ * [Fix] Fix `%sqlcmd columns` in MySQL and MariaDB * [Doc] Updating connecting guide (by @DaveOkpare) (#56) * [Feature] Upgrades SQLAlchemy version to 2 -* [Feature] Informative error messages when query execution fails +* [Feature] Informative error messages when query execution fails due to syntax errors, or postgres password not provided. ## 0.7.0 (2023-04-05) diff --git a/src/sql/error_message.py b/src/sql/error_message.py index 8ac0cc232..6d1ffc76b 100644 --- a/src/sql/error_message.py +++ b/src/sql/error_message.py @@ -1,10 +1,31 @@ -def detail(msg): - if "fe_sendauth: no password supplied" in str(msg): +import sqlglot + + +def detail(msg, query): + msg = str(msg) + return_msg = f"Looks like there is some syntax error " f"in your query : {query} \n" + if "syntax error" in msg: + try: + parse = sqlglot.transpile(query) + if query.upper().replace(";", "").strip() not in [ + suggestion.upper() for suggestion in parse + ]: + return return_msg + f"Did you mean : {parse} " + except sqlglot.errors.ParseError as e: + err = e.errors + for item in err: + return_msg += ( + f"Syntax Error: {item['description']} at " + f"Line {item['line']}, Column {item['col']}\n" + ) + return return_msg + + if "fe_sendauth: no password supplied" in msg: return ( "Looks like you have run into some issues. " - "Here's some information on connecting to database using URL strings: " + "Review our DB connection via URL strings guide: " "https://jupysql.ploomber.io/en/latest/connecting.html ." - " If you are working on Ubuntu you might want to check this guide: " + " Using Ubuntu? Check out this guide: " "https://help.ubuntu.com/community/PostgreSQL#fe_sendauth:_" "no_password_supplied" ) diff --git a/src/sql/magic.py b/src/sql/magic.py index d615522c9..9715ee40a 100644 --- a/src/sql/magic.py +++ b/src/sql/magic.py @@ -407,7 +407,7 @@ def interactive_execute_wrapper(**kwargs): # JA: added DatabaseError for MySQL except (ProgrammingError, OperationalError, DatabaseError) as e: # Sqlite apparently return all errors as OperationalError :/ - detailed_msg = detail(e) + detailed_msg = detail(e, command.sql) if self.short_errors: print(e) diff --git a/src/tests/test_error_messages.py b/src/tests/test_error_messages.py new file mode 100644 index 000000000..67bde52ad --- /dev/null +++ b/src/tests/test_error_messages.py @@ -0,0 +1,73 @@ +import pytest + +from sqlalchemy.exc import OperationalError + + +def test_syntax_error_no_suggestion(ip, capsys): + ip.run_cell_magic( + "sql", + "", + """ + sqlite:// + SELECT FROM author; + """, + ) + out, _ = capsys.readouterr() + assert '(sqlite3.OperationalError) near "FROM": syntax error' in out + assert ( + "If you need help solving this issue, " + "send us a message: https://ploomber.io/community" in out + ) + + +def test_syntax_error_description(ip, capsys): + ip.run_cell_magic( + "sql", + "", + """ + sqlite:// + SELECT first_(name FROM author; + """, + ) + out, _ = capsys.readouterr() + assert '(sqlite3.OperationalError) near "FROM": syntax error' in out + assert "Syntax Error: Expecting ) at Line 1, Column 20" in out + assert ( + "If you need help solving this issue, " + "send us a message: https://ploomber.io/community" in out + ) + + +def test_syntax_error_suggestion(ip, capsys): + ip.run_cell_magic( + "sql", + "", + """ + sqlite:// + ALTER TABLE author RENAME new_author; + """, + ) + out, _ = capsys.readouterr() + assert "Did you mean : ['ALTER TABLE author RENAME TO new_author']" in out + assert ( + "If you need help solving this issue, " + "send us a message: https://ploomber.io/community" in out + ) + + +def test_query_syntax_error(ip, capsys): + ip.run_line_magic("config", "SqlMagic.short_errors = False") + with pytest.raises(OperationalError): + ip.run_cell_magic( + "sql", + "", + """ + sqlite:// + SELECT FROM author; + """, + ) + out, _ = capsys.readouterr() + assert ( + "If you need help solving this issue, " + "send us a message: https://ploomber.io/community" in out + ) diff --git a/src/tests/test_magic.py b/src/tests/test_magic.py index c8b369926..1281812fe 100644 --- a/src/tests/test_magic.py +++ b/src/tests/test_magic.py @@ -10,7 +10,7 @@ import polars as pl import pytest from sqlalchemy import create_engine -from sqlalchemy.exc import OperationalError + from IPython.core.error import UsageError from sql.connection import Connection from sql.magic import SqlMagic @@ -796,38 +796,3 @@ def test_interact_and_missing_ipywidgets_installed(ip): "%sql --interact my_variable SELECT * FROM author LIMIT {{my_variable}}" ) assert isinstance(out.error_in_exec, ModuleNotFoundError) - - -def test_query_syntax_error_short(ip, capsys): - ip.run_cell_magic( - "sql", - "", - """ - sqlite:// - SELECT FROM author; - """, - ) - out, _ = capsys.readouterr() - assert '(sqlite3.OperationalError) near "FROM": syntax error' in out - assert ( - "If you need help solving this issue, " - "send us a message: https://ploomber.io/community" in out - ) - - -def test_query_syntax_error(ip, capsys): - ip.run_line_magic("config", "SqlMagic.short_errors = False") - with pytest.raises(OperationalError): - ip.run_cell_magic( - "sql", - "", - """ - sqlite:// - SELECT FROM author; - """, - ) - out, _ = capsys.readouterr() - assert ( - "If you need help solving this issue, " - "send us a message: https://ploomber.io/community" in out - )