Repository: mindsdb/mindsdb

Issue:
Don't create datasource if training fails
Example query:
```
CREATE PREDICTOR home_rentals_model FROM postgres (SELECT * from test_data.home_rentals)
 as ds_name PREDICT rental_price;
```

If somehow, the model training fails and returned the error the datasource from the alias name `ds_name` will still be created.


Discussion:

Resolving patch:
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
@@ -558,7 +558,7 @@ def answer_create_predictor(self, struct):
             else:
                 kwargs['timeseries_settings'].update(timeseries_settings)
 
-        model_interface.learn(predictor_name, ds, predict, ds_data['id'], kwargs=kwargs)
+        model_interface.learn(predictor_name, ds, predict, ds_data['id'], kwargs=kwargs, delete_ds_on_fail=True)
 
         self.packet(OkPacket).send()
 
diff --git a/mindsdb/interfaces/model/learn_process.py b/mindsdb/interfaces/model/learn_process.py
--- a/mindsdb/interfaces/model/learn_process.py
+++ b/mindsdb/interfaces/model/learn_process.py
@@ -2,6 +2,7 @@
 import traceback
 import tempfile
 from pathlib import Path
+from typing import Optional
 
 import pandas as pd
 from pandas.core.frame import DataFrame
@@ -13,7 +14,7 @@
 import mindsdb.interfaces.storage.db as db
 from mindsdb.interfaces.database.database import DatabaseWrapper
 from mindsdb.interfaces.model.model_interface import ModelInterface, ModelInterfaceWrapper
-from mindsdb.interfaces.storage.db import session, Predictor
+from mindsdb.interfaces.storage.db import session, Predictor, Datasource
 from mindsdb import __version__ as mindsdb_version
 from mindsdb.interfaces.datastore.datastore import DataStore, DataStoreWrapper
 from mindsdb.interfaces.storage.fs import FsStore
@@ -44,7 +45,7 @@ def run_generate(df: DataFrame, problem_definition: ProblemDefinition, predictor
     json_ai = lightwood.json_ai_from_problem(df, problem_definition)
     code = lightwood.code_from_json_ai(json_ai)
 
-    predictor_record = Predictor.query.get(predictor_id)
+    predictor_record = Predictor.query.with_for_update().get(predictor_id)
     predictor_record.json_ai = json_ai.to_dict()
     predictor_record.code = code
     db.session.commit()
@@ -53,7 +54,7 @@ def run_generate(df: DataFrame, problem_definition: ProblemDefinition, predictor
 @mark_process(name='learn')
 def run_fit(predictor_id: int, df: pd.DataFrame) -> None:
     try:
-        predictor_record = Predictor.query.get(predictor_id)
+        predictor_record = Predictor.query.with_for_update().get(predictor_id)
         assert predictor_record is not None
 
         fs_store = FsStore()
@@ -86,9 +87,23 @@ def run_fit(predictor_id: int, df: pd.DataFrame) -> None:
         raise e
 
 
-def run_learn(df: DataFrame, problem_definition: ProblemDefinition, predictor_id: int) -> None:
-    run_generate(df, problem_definition, predictor_id)
-    run_fit(predictor_id, df)
+def run_learn(df: DataFrame, problem_definition: ProblemDefinition, predictor_id: int,
+              delete_ds_on_fail: Optional[bool] = False) -> None:
+    try:
+        run_generate(df, problem_definition, predictor_id)
+        run_fit(predictor_id, df)
+    except Exception:
+        if delete_ds_on_fail is True:
+            predictor_record = Predictor.query.with_for_update().get(predictor_id)
+            linked_db_ds = Datasource.query.filter_by(id=predictor_record.datasource_id).first()
+            if linked_db_ds is not None:
+                predictors_with_ds = Predictor.query.filter(
+                    (Predictor.id != predictor_id) & (Predictor.datasource_id == linked_db_ds.id)
+                ).all()
+                if len(predictors_with_ds) == 0:
+                    session.delete(linked_db_ds)
+                    predictor_record.datasource_id = None
+            session.commit()
 
 
 def run_adjust(name, db_name, from_data, datasource_id, company_id):
diff --git a/mindsdb/interfaces/model/model_controller.py b/mindsdb/interfaces/model/model_controller.py
--- a/mindsdb/interfaces/model/model_controller.py
+++ b/mindsdb/interfaces/model/model_controller.py
@@ -106,7 +106,8 @@ def _unpack_old_args(self, from_data: dict, kwargs: dict, to_predict: Optional[U
         return df, problem_definition, join_learn_process
 
     @mark_process(name='learn')
-    def learn(self, name: str, from_data: dict, to_predict: str, datasource_id: int, kwargs: dict, company_id: int) -> None:
+    def learn(self, name: str, from_data: dict, to_predict: str, datasource_id: int, kwargs: dict,
+              company_id: int, delete_ds_on_fail: Optional[bool] = False) -> None:
         predictor_record = db.session.query(db.Predictor).filter_by(company_id=company_id, name=name).first()
         if predictor_record is not None:
             raise Exception('Predictor name must be unique.')
@@ -129,7 +130,7 @@ def learn(self, name: str, from_data: dict, to_predict: str, datasource_id: int,
         db.session.commit()
         predictor_id = predictor_record.id
 
-        p = LearnProcess(df, problem_definition, predictor_id)
+        p = LearnProcess(df, problem_definition, predictor_id, delete_ds_on_fail)
         p.start()
         if join_learn_process:
             p.join()


Test patch: