Repository: mindsdb/mindsdb

Issue:
Incomplete cleanup after unsuccessful DS creation
If during DS creation any Exception is occurred, then it will be not possible create DS with this name again, because we still have data for it in db.


Discussion:
The reason is sqllite autoincrement behavior https://www.sqlite.org/autoinc.html
It is reuse empty numbers. Therefore it is possible get situation: we had predictor training using sql api, during it temp DS was creating. After training finished we are removing temp datasource and so its ID releasing. When we next time trying to add DS, sqllite will try to use old id, and this brings error in good case, in bad case old predictor could be linked with new ds.

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
@@ -20,7 +20,6 @@
 import atexit
 import tempfile
 import datetime
-import time
 import socket
 import struct
 from collections import OrderedDict
@@ -531,16 +530,17 @@ def answer_create_predictor(self, struct):
         data_store = self.session.data_store
         company_id = self.session.company_id
 
-        if get_db_integration(struct['integration_name'], company_id) is None:
-            raise Exception(f"Unknown integration: {struct['integration_name']}")
+        predictor_name = struct['predictor_name']
+        integration_name = struct['integration_name']
+
+        if get_db_integration(integration_name, company_id) is None:
+            raise Exception(f"Unknown integration: {integration_name}")
 
-        is_temp_ds = False
         ds_name = struct.get('datasource_name')
         if ds_name is None:
-            ds_name = data_store.get_vacant_name('temp')
-            is_temp_ds = True
+            ds_name = data_store.get_vacant_name(predictor_name)
 
-        ds = data_store.save_datasource(ds_name, struct['integration_name'], {'query': struct['select']})
+        ds = data_store.save_datasource(ds_name, integration_name, {'query': struct['select']})
         ds_data = data_store.get_datasource(ds_name)
 
         # TODO add alias here
@@ -558,13 +558,7 @@ def answer_create_predictor(self, struct):
             else:
                 kwargs['timeseries_settings'].update(timeseries_settings)
 
-        model_interface.learn(struct['predictor_name'], ds, predict, ds_data['id'], kwargs=kwargs)
-
-        if is_temp_ds:
-            try:
-                data_store.delete_datasource(ds_name)
-            except Exception:
-                pass
+        model_interface.learn(predictor_name, ds, predict, ds_data['id'], kwargs=kwargs)
 
         self.packet(OkPacket).send()
 
diff --git a/mindsdb/integrations/base/integration.py b/mindsdb/integrations/base/integration.py
--- a/mindsdb/integrations/base/integration.py
+++ b/mindsdb/integrations/base/integration.py
@@ -87,7 +87,7 @@ def _loop(self):
                         else:
                             # Bad action value
                             log.error('INTEGRATION %s: bad action value received - %s', self.name, dct)
-                
+
             stream_db_recs = db.session.query(db.Stream).filter_by(
                 company_id=self.company_id,
                 integration=self.name
diff --git a/mindsdb/interfaces/datastore/datastore.py b/mindsdb/interfaces/datastore/datastore.py
--- a/mindsdb/interfaces/datastore/datastore.py
+++ b/mindsdb/interfaces/datastore/datastore.py
@@ -136,8 +136,8 @@ def get_vacant_name(self, base=None, company_id=None):
         '''
         if base is None:
             base = 'datasource'
-        datasources = session.query(Datasource).filter_by(company_id=company_id).all()
-        datasources_names = [x.name for x in datasources]
+        datasources = session.query(Datasource.name).filter_by(company_id=company_id).all()
+        datasources_names = [x[0] for x in datasources]
         if base not in datasources_names:
             return base
         for i in range(1, 1000):


Test patch: