Repository: rotki/rotki

Issue:
Make query ranges per exchange
## Abstract

Currently query ranges are per location (so all kucoin exchanges will have same query ranges, all binance exchanges, etc.)

## Motivation

Improve code.

## Specification

- [ ] Make query ranges per specific exchange instead of current per-location approach


Discussion:

Resolving patch:
diff --git a/rotkehlchen/db/dbhandler.py b/rotkehlchen/db/dbhandler.py
--- a/rotkehlchen/db/dbhandler.py
+++ b/rotkehlchen/db/dbhandler.py
@@ -980,11 +980,19 @@ def get_used_query_range(self, cursor: 'DBCursor', name: str) -> Optional[tuple[
 
         return Timestamp(int(result[0])), Timestamp(int(result[1]))
 
-    def delete_used_query_range_for_exchange(self, write_cursor: 'DBCursor', location: Location) -> None:  # noqa: E501
+    def delete_used_query_range_for_exchange(
+            self,
+            write_cursor: 'DBCursor',
+            location: Location,
+            exchange_name: Optional[str] = None,
+    ) -> None:
         """Delete the query ranges for the given exchange name"""
+        names_to_delete = f'{str(location)}\\_%'
+        if exchange_name is not None:
+            names_to_delete += f'\\_{exchange_name}'
         write_cursor.execute(
             'DELETE FROM used_query_ranges WHERE name LIKE ? ESCAPE ?;',
-            (f'{str(location)}\\_%', '\\'),
+            (names_to_delete, '\\'),
         )
 
     def purge_exchange_data(self, write_cursor: 'DBCursor', location: Location) -> None:
@@ -1559,7 +1567,7 @@ def edit_exchange(
                 # from the possible new pairs
                 write_cursor.execute(
                     'DELETE FROM used_query_ranges WHERE name LIKE ? ESCAPE ?;',
-                    (f'{str(location)}\\_trades_%', '\\'),
+                    (f'{str(location)}\\_trades\\_{name}', '\\'),
                 )
             except sqlcipher.DatabaseError as e:  # pylint: disable=no-member
                 raise InputError(f'Could not update DB user_credentials_mappings due to {str(e)}') from e  # noqa: E501
diff --git a/rotkehlchen/exchanges/manager.py b/rotkehlchen/exchanges/manager.py
--- a/rotkehlchen/exchanges/manager.py
+++ b/rotkehlchen/exchanges/manager.py
@@ -159,15 +159,17 @@ def delete_exchange(self, name: str, location: Location) -> tuple[bool, str]:
         if exchanges_list is None:
             return False, f'{str(location)} exchange {name} is not registered'
 
-        last_in_location = len(exchanges_list) == 1
-        if last_in_location is True:
+        if len(exchanges_list) == 1:  # if is last exchange of this location
             self.connected_exchanges.pop(location)
         else:
             self.connected_exchanges[location] = [x for x in exchanges_list if x.name != name]
         with self.database.user_write() as write_cursor:  # Also remove it from the db
             self.database.remove_exchange(write_cursor=write_cursor, name=name, location=location)  # noqa: E501
-            if last_in_location is True:
-                self.database.delete_used_query_range_for_exchange(write_cursor=write_cursor, location=location)  # noqa: E501
+            self.database.delete_used_query_range_for_exchange(
+                write_cursor=write_cursor,
+                location=location,
+                exchange_name=name,
+            )
         return True, ''
 
     def delete_all_exchanges(self) -> None:


Test patch:
diff --git a/rotkehlchen/tests/api/test_exchanges.py b/rotkehlchen/tests/api/test_exchanges.py
--- a/rotkehlchen/tests/api/test_exchanges.py
+++ b/rotkehlchen/tests/api/test_exchanges.py
@@ -401,7 +401,12 @@ def test_remove_exchange(rotkehlchen_api_server):
     rotki = rotkehlchen_api_server.rest_api.rotkehlchen
     db = rotki.data.db
     # Setup coinbase exchange
-    data = {'location': 'coinbase', 'name': 'foo', 'api_key': 'ddddd', 'api_secret': 'fffffff'}
+    data = {
+        'location': 'coinbase',
+        'name': 'Coinbase 1',
+        'api_key': 'ddddd',
+        'api_secret': 'fffffff',
+    }
     with mock_validate_api_key_success(Location.COINBASE):
         response = requests.put(
             api_url_for(rotkehlchen_api_server, 'exchangesresource'), json=data,
@@ -410,25 +415,28 @@ def test_remove_exchange(rotkehlchen_api_server):
     # and check it's registered
     response = requests.get(api_url_for(rotkehlchen_api_server, 'exchangesresource'))
     result = assert_proper_response_with_result(response)
-    assert result == [{'location': 'coinbase', 'name': 'foo'}]
+    assert result == [{'location': 'coinbase', 'name': 'Coinbase 1'}]
 
     # Add query ranges to see that they also get deleted when removing the exchange
     cursor = db.conn.cursor()
     cursor.executemany(
         'INSERT OR REPLACE INTO used_query_ranges(name, start_ts, end_ts) VALUES (?, ?, ?)',
-        [('coinbasepro_trades', 0, 1579564096),
-         ('coinbasepro_margins', 0, 1579564096),
-         ('coinbasepro_asset_movements', 0, 1579564096),
-         ('coinbase_trades', 0, 1579564096),
-         ('coinbase_margins', 0, 1579564096),
-         ('coinbase_asset_movements', 0, 1579564096),
-         ('binance_trades', 0, 1579564096),
-         ('binance_margins', 0, 1579564096),
-         ('binance_asset_movements', 0, 1579564096)],
+        [('coinbasepro_trades_CoinbasePro 1', 0, 1579564096),
+         ('coinbasepro_margins_CoinbasePro 1', 0, 1579564096),
+         ('coinbasepro_asset_movements_CoinbasePro 1', 0, 1579564096),
+         ('coinbase_trades_Coinbase 1', 0, 1579564096),
+         ('coinbase_margins_Coinbase 1', 0, 1579564096),
+         ('coinbase_asset_movements_Coinbase 1', 0, 1579564096),
+         ('coinbase_trades_Coinbase 2', 0, 1579564096),
+         ('coinbase_margins_Coinbase 2', 0, 1579564096),
+         ('coinbase_asset_movements_Coinbase 2', 0, 1579564096),
+         ('binance_trades_Binance 1', 0, 1579564096),
+         ('binance_margins_Binance 1', 0, 1579564096),
+         ('binance_asset_movements_Binance 1', 0, 1579564096)],
     )
 
     # Now remove the registered coinbase exchange
-    data = {'location': 'coinbase', 'name': 'foo'}
+    data = {'location': 'coinbase', 'name': 'Coinbase 1'}
     response = requests.delete(api_url_for(rotkehlchen_api_server, 'exchangesresource'), json=data)
     assert_simple_ok_response(response)
     # and check that it's not registered anymore
@@ -441,9 +449,9 @@ def test_remove_exchange(rotkehlchen_api_server):
     count = 0
     for entry in result:
         count += 1
-        msg = 'only binance or coinbasepro query ranges should remain'
-        assert 'binance' in entry[0] or 'coinbasepro' in entry[0], msg
-    assert count == 6, 'only 6 query ranges should remain in the DB'
+        msg = 'only binance or coinbasepro or Coinbase 2 query ranges should remain'
+        assert 'binance' in entry[0] or 'coinbasepro' in entry[0] or 'Coinbase 2' in entry[0], msg
+    assert count == 9, 'only 9 query ranges should remain in the DB'
 
     # now try to remove a non-registered exchange
     data = {'location': 'binance', 'name': 'my_binance'}