From d1d90c865e2f88728aab2b666323db07d58116ab Mon Sep 17 00:00:00 2001
From: Georgy Moshkin <gmoshkin@picodata.io>
Date: Fri, 31 May 2024 18:49:09 +0300
Subject: [PATCH] fix: pretty sql parsing error messages

Previously when running `pico.sql [[ create table foo ]]` we would get
this output:
```
---
- null
- "sbroad: rule parsing error:  --> 1:9\n  |\n1 |  create table foo \n  |  ^---\n
  \ |\n  = expected Unique"
...
```

But now the output will be:
```
---
- null
- |+
  sbroad: rule parsing error:  --> 1:9
    |
  1 |  create table foo
    |         ^---
    |
    = expected Unique

...
```
---
 src/sql.rs              | 25 ++++++++++++++++++++++++-
 src/sql/init.lua        |  2 +-
 test/int/test_cli_ux.py | 27 +++++++++++++++++++++++++++
 test/int/test_sql.py    |  4 ++--
 4 files changed, 54 insertions(+), 4 deletions(-)

diff --git a/src/sql.rs b/src/sql.rs
index 7e97897205..d224de318c 100644
--- a/src/sql.rs
+++ b/src/sql.rs
@@ -282,7 +282,30 @@ pub fn with_tracer(ctx: Context, tracer_kind: TracerKind) -> Context {
 /// Dispatches a query to the cluster.
 #[proc(packed_args)]
 pub fn dispatch_query(encoded_params: EncodedPatternWithParams) -> traft::Result<Tuple> {
-    dispatch_sql_query(encoded_params)
+    let res = dispatch_sql_query(encoded_params);
+    res.map_err(|e| {
+        match e {
+            Error::Sbroad(SbroadError::ParsingError(entity, message)) if message.contains('\n') => {
+                // Tweak the error message so that tarantool's yaml handler
+                // prints it in human-readable form
+                //
+                // `+ 1` here for one extra '\n' at the end
+                let mut buffer = String::with_capacity(message.len() + 1);
+                for line in message.lines() {
+                    // There must not be any spaces at the end of lines,
+                    // otherwise the string will be formatted incorrectly
+                    buffer.push_str(line.trim_end());
+                    buffer.push('\n');
+                }
+                // There must be at least one empty line so that tarantool
+                // formats the string correctly (it's a special hack they use
+                // for the help feature in the lua console).
+                buffer.push('\n');
+                SbroadError::ParsingError(entity, buffer.into()).into()
+            }
+            e => e,
+        }
+    })
 }
 
 pub fn dispatch_sql_query(encoded_params: EncodedPatternWithParams) -> traft::Result<Tuple> {
diff --git a/src/sql/init.lua b/src/sql/init.lua
index 7700f8b93f..963bddadc1 100644
--- a/src/sql/init.lua
+++ b/src/sql/init.lua
@@ -43,7 +43,7 @@ local function sql(...)
     )
 
     if ok == false then
-        return nil, res
+        return nil, tostring(res)
     end
 
     return helper.format_result(res[1])
diff --git a/test/int/test_cli_ux.py b/test/int/test_cli_ux.py
index 3989aa8179..5612830f07 100644
--- a/test/int/test_cli_ux.py
+++ b/test/int/test_cli_ux.py
@@ -234,3 +234,30 @@ def test_sql_explain_ok(cluster: Cluster):
     cli.expect_exact("execution options:")
     cli.expect_exact("sql_vdbe_max_steps = 45000")
     cli.expect_exact("vtable_max_rows = 5000")
+
+
+def test_lua_console_sql_error_messages(cluster: Cluster):
+    i1 = cluster.add_instance(wait_online=True)
+
+    result = i1.eval(
+        """
+        console = require 'console'
+        return console.eval ' pico.sql [[ create table foo ]] '
+        """
+    )
+
+    assert (
+        result
+        == """---
+- null
+- |+
+  sbroad: rule parsing error:  --> 1:9
+    |
+  1 |  create table foo
+    |         ^---
+    |
+    = expected Unique
+
+...
+"""
+    )
diff --git a/test/int/test_sql.py b/test/int/test_sql.py
index 451d160a77..07cd864d03 100644
--- a/test/int/test_sql.py
+++ b/test/int/test_sql.py
@@ -3466,7 +3466,7 @@ def test_rename_user(cluster: Cluster):
     # Existed name
     with pytest.raises(
         ReturnError,
-        match=f'User with name "{biba}" exists. Unable to rename user "{boba}".',
+        match=f'User with name "{biba}" exists. Unable to rename user "{boba}"',
     ):
         data = i1.sudo_sql(
             f"""
@@ -3479,7 +3479,7 @@ def test_rename_user(cluster: Cluster):
     with pytest.raises(
         ReturnError,
         match="""\
-box error: AccessDenied: Alter access to user 'boba' is denied for user 'biba'.\
+box error: AccessDenied: Alter access to user 'boba' is denied for user 'biba'\
 """,
     ):
         data = i1.sql(
-- 
GitLab