Repository: mindsdb/mindsdb

Issue:
[Feat] Better error messaging for mysql api (add)
In additional to https://github.com/mindsdb/mindsdb/issues/1861
We need understandable error messages for various users mistakes. For example:
`show unknown;` -> return `unknown command` or something;
`drop predictor unexists;` -> instead of `Predictor 'None@@@@@unexists' does not exist` return `Predictor 'unexists' does not exist`;
`create predictor pname from xxx (select * from www)`  - return `'predict' is mandatory`
`create predictor 1pname from xxx (select * from www) predict field` - name must not start form number
`select * from mindsdb.pname where unexist=100;` - instead of `error in apply predictor row step` return `column "unexist" unexist`
`select unexists from mindsdb.pname where sqft=1000;` - return `column "unexist" unexist`
and so on


Discussion:
@ilia-tsyplenkov how are you doing here?
If I may suggest, I think that "x doesn't exist" would be more usual than "x unexist".
@torrmal I'll fix next Monday

Resolving patch:
diff --git a/mindsdb/api/mysql/mysql_proxy/classes/sql_query.py b/mindsdb/api/mysql/mysql_proxy/classes/sql_query.py
--- a/mindsdb/api/mysql/mysql_proxy/classes/sql_query.py
+++ b/mindsdb/api/mysql/mysql_proxy/classes/sql_query.py
@@ -1045,7 +1045,7 @@ def execute_step(self, step, steps_data):
                                     else:
                                         raise Exception(f'Can not find column "{column_name}" in table "{table_name}"')
                             if appropriate_table is None:
-                                raise Exception(f'Can not find approproate table for column {column_name}')
+                                raise SqlApiException(f'Can not find appropriate table for column {column_name}')
 
                             columns_to_copy = None
                             table_column_names_list = [x[1] or x[0] for x in table_columns]
@@ -1055,8 +1055,8 @@ def execute_step(self, step, steps_data):
                                     columns_to_copy = column
                                     break
                             else:
-                                raise Exception(
-                                    f'Can not find approproate column in data: {(column_name, column_alias)}')
+                                raise ErKeyColumnDoesNotExist(
+                                    f'Can not find appropriate column in data: {(column_name, column_alias)}')
 
                             for row in step_data['values']:
                                 row[appropriate_table][(column_name, column_alias)] = row[appropriate_table][
@@ -1070,21 +1070,28 @@ def execute_step(self, step, steps_data):
                                        alias=column_alias)
                             )
                         else:
-                            raise Exception('Undefined column name')
+                            raise SqlApiException('Undefined column name')
 
                         # if column not exists in result - copy value to it
                         if (column_name, column_alias) not in step_data['columns'][appropriate_table]:
                             step_data['columns'][appropriate_table].append((column_name, column_alias))
                             for row in step_data['values']:
                                 if (column_name, column_alias) not in row[appropriate_table]:
-                                    row[appropriate_table][(column_name, column_alias)] = row[appropriate_table][(column_name, column_name)]
+                                    try:
+                                        row[appropriate_table][(column_name, column_alias)] = row[appropriate_table][(column_name, column_name)]
+                                    except KeyError:
+                                        raise ErKeyColumnDoesNotExist(f'Unknown column: {column_name}')
+
                     else:
-                        raise Exception(f'Unexpected column name type: {column_identifier}')
+                        raise ErKeyColumnDoesNotExist(f'Unknown column type: {column_identifier}')
 
                 self.columns_list = columns_list
                 data = step_data
             except Exception as e:
-                raise SqlApiException(f'error on project step:{e} ') from e
+                if isinstance(e, SqlApiException):
+                    raise e
+                raise SqlApiException(f'error on project step: {e} ') from e
+
         elif type(step) == SaveToTable:
             step_data = step.dataframe.result_data
             integration_name = step.table.parts[0]
diff --git a/mindsdb/api/mysql/mysql_proxy/mysql_proxy.py b/mindsdb/api/mysql/mysql_proxy/mysql_proxy.py
--- a/mindsdb/api/mysql/mysql_proxy/mysql_proxy.py
+++ b/mindsdb/api/mysql/mysql_proxy/mysql_proxy.py
@@ -1378,10 +1378,22 @@ def process_query(self, sql):
                 statement = parse_sql(sql, dialect='mindsdb')
             except Exception:
                 statement = parse_sql(sql, dialect='mysql')
-        except Exception:
+        except Exception as e:
             # not all statemts are parse by parse_sql
             log.warning(f'SQL statement are not parsed by mindsdb_sql: {sql}')
-            pass
+            lower_sql = sql.lower().replace('\t', ' ')
+
+            sql_list = [x for x in lower_sql.split(' ') if x not in ('', ' ')]
+            if len(sql_list) > 1 and sql_list[0] == "show":
+                    raise SqlApiException(f"unknown command: {sql}")
+            if len(sql_list) > 2 and " ".join(sql_list[:2]) == "create predictor":
+                if 'predict' not in sql_list:
+                    raise SqlApiException(f"'predict' field is mandatory: {sql}")
+                # analyze predictor name
+                if not sql_list[2][0].isalpha():
+                    raise SqlApiException(f"predictor name must start from letter character: {sql}")
+
+            raise SqlApiException(f'SQL statement cannot be parsed by mindsdb_sql - {sql}: {e}') from e
 
         if type(statement) == CreateDatasource:
             struct = {


Test patch: