@@ -1428,3 +1428,185 @@ async def test_benchmark_batch_insert_speed(
1428
1428
tree_id = tree_id ,
1429
1429
changelist = batch ,
1430
1430
)
1431
+
1432
+
1433
+ @pytest .mark .anyio
1434
+ async def test_delete_store_data (raw_data_store : DataStore ) -> None :
1435
+ tree_id = bytes32 (b"\0 " * 32 )
1436
+ tree_id_2 = bytes32 (b"\0 " * 31 + b"\1 " )
1437
+ await raw_data_store .create_tree (tree_id = tree_id , status = Status .COMMITTED )
1438
+ await raw_data_store .create_tree (tree_id = tree_id_2 , status = Status .COMMITTED )
1439
+ total_keys = 4
1440
+ keys = [key .to_bytes (4 , byteorder = "big" ) for key in range (total_keys )]
1441
+ batch1 = [
1442
+ {"action" : "insert" , "key" : keys [0 ], "value" : keys [0 ]},
1443
+ {"action" : "insert" , "key" : keys [1 ], "value" : keys [1 ]},
1444
+ ]
1445
+ batch2 = batch1 .copy ()
1446
+ batch1 .append ({"action" : "insert" , "key" : keys [2 ], "value" : keys [2 ]})
1447
+ batch2 .append ({"action" : "insert" , "key" : keys [3 ], "value" : keys [3 ]})
1448
+ assert batch1 != batch2
1449
+ await raw_data_store .insert_batch (tree_id , batch1 , status = Status .COMMITTED )
1450
+ await raw_data_store .insert_batch (tree_id_2 , batch2 , status = Status .COMMITTED )
1451
+ keys_values_before = await raw_data_store .get_keys_values (tree_id_2 )
1452
+ async with raw_data_store .db_wrapper .reader () as reader :
1453
+ result = await reader .execute ("SELECT * FROM node" )
1454
+ nodes = await result .fetchall ()
1455
+ kv_nodes_before = {}
1456
+ for node in nodes :
1457
+ if node ["key" ] is not None :
1458
+ kv_nodes_before [node ["key" ]] = node ["value" ]
1459
+ assert [kv_nodes_before [key ] for key in keys ] == keys
1460
+ await raw_data_store .delete_store_data (tree_id )
1461
+ # Deleting from `node` table doesn't alter other stores.
1462
+ keys_values_after = await raw_data_store .get_keys_values (tree_id_2 )
1463
+ assert keys_values_before == keys_values_after
1464
+ async with raw_data_store .db_wrapper .reader () as reader :
1465
+ result = await reader .execute ("SELECT * FROM node" )
1466
+ nodes = await result .fetchall ()
1467
+ kv_nodes_after = {}
1468
+ for node in nodes :
1469
+ if node ["key" ] is not None :
1470
+ kv_nodes_after [node ["key" ]] = node ["value" ]
1471
+ for i in range (total_keys ):
1472
+ if i != 2 :
1473
+ assert kv_nodes_after [keys [i ]] == keys [i ]
1474
+ else :
1475
+ # `keys[2]` was only present in the first store.
1476
+ assert keys [i ] not in kv_nodes_after
1477
+ assert not await raw_data_store .tree_id_exists (tree_id )
1478
+ await raw_data_store .delete_store_data (tree_id_2 )
1479
+ async with raw_data_store .db_wrapper .reader () as reader :
1480
+ async with reader .execute ("SELECT COUNT(*) FROM node" ) as cursor :
1481
+ row_count = await cursor .fetchone ()
1482
+ assert row_count is not None
1483
+ assert row_count [0 ] == 0
1484
+ assert not await raw_data_store .tree_id_exists (tree_id_2 )
1485
+
1486
+
1487
+ @pytest .mark .anyio
1488
+ async def test_delete_store_data_multiple_stores (raw_data_store : DataStore ) -> None :
1489
+ # Make sure inserting and deleting the same data works
1490
+ for repetition in range (2 ):
1491
+ num_stores = 50
1492
+ total_keys = 150
1493
+ keys_deleted_per_store = 3
1494
+ tree_ids = [bytes32 (i .to_bytes (32 , byteorder = "big" )) for i in range (num_stores )]
1495
+ for tree_id in tree_ids :
1496
+ await raw_data_store .create_tree (tree_id = tree_id , status = Status .COMMITTED )
1497
+ original_keys = [key .to_bytes (4 , byteorder = "big" ) for key in range (total_keys )]
1498
+ batches = []
1499
+ for i in range (num_stores ):
1500
+ batch = [
1501
+ {"action" : "insert" , "key" : key , "value" : key } for key in original_keys [i * keys_deleted_per_store :]
1502
+ ]
1503
+ batches .append (batch )
1504
+
1505
+ for tree_id , batch in zip (tree_ids , batches ):
1506
+ await raw_data_store .insert_batch (tree_id , batch , status = Status .COMMITTED )
1507
+
1508
+ for tree_index in range (num_stores ):
1509
+ async with raw_data_store .db_wrapper .reader () as reader :
1510
+ result = await reader .execute ("SELECT * FROM node" )
1511
+ nodes = await result .fetchall ()
1512
+
1513
+ keys = {node ["key" ] for node in nodes if node ["key" ] is not None }
1514
+ assert len (keys ) == total_keys - tree_index * keys_deleted_per_store
1515
+ keys_after_index = set (original_keys [tree_index * keys_deleted_per_store :])
1516
+ keys_before_index = set (original_keys [: tree_index * keys_deleted_per_store ])
1517
+ assert keys_after_index .issubset (keys )
1518
+ assert keys .isdisjoint (keys_before_index )
1519
+ await raw_data_store .delete_store_data (tree_ids [tree_index ])
1520
+
1521
+ async with raw_data_store .db_wrapper .reader () as reader :
1522
+ async with reader .execute ("SELECT COUNT(*) FROM node" ) as cursor :
1523
+ row_count = await cursor .fetchone ()
1524
+ assert row_count is not None
1525
+ assert row_count [0 ] == 0
1526
+
1527
+
1528
+ @pytest .mark .parametrize ("common_keys_count" , [1 , 250 , 499 ])
1529
+ @pytest .mark .anyio
1530
+ async def test_delete_store_data_with_common_values (raw_data_store : DataStore , common_keys_count : int ) -> None :
1531
+ tree_id_1 = bytes32 (b"\x00 " * 31 + b"\x01 " )
1532
+ tree_id_2 = bytes32 (b"\x00 " * 31 + b"\x02 " )
1533
+
1534
+ await raw_data_store .create_tree (tree_id = tree_id_1 , status = Status .COMMITTED )
1535
+ await raw_data_store .create_tree (tree_id = tree_id_2 , status = Status .COMMITTED )
1536
+
1537
+ key_offset = 1000
1538
+ total_keys_per_store = 500
1539
+ assert common_keys_count < key_offset
1540
+ common_keys = {key .to_bytes (4 , byteorder = "big" ) for key in range (common_keys_count )}
1541
+ unique_keys_1 = {
1542
+ (key + key_offset ).to_bytes (4 , byteorder = "big" ) for key in range (total_keys_per_store - common_keys_count )
1543
+ }
1544
+ unique_keys_2 = {
1545
+ (key + (2 * key_offset )).to_bytes (4 , byteorder = "big" ) for key in range (total_keys_per_store - common_keys_count )
1546
+ }
1547
+
1548
+ batch1 = [{"action" : "insert" , "key" : key , "value" : key } for key in common_keys .union (unique_keys_1 )]
1549
+ batch2 = [{"action" : "insert" , "key" : key , "value" : key } for key in common_keys .union (unique_keys_2 )]
1550
+
1551
+ await raw_data_store .insert_batch (tree_id_1 , batch1 , status = Status .COMMITTED )
1552
+ await raw_data_store .insert_batch (tree_id_2 , batch2 , status = Status .COMMITTED )
1553
+
1554
+ await raw_data_store .delete_store_data (tree_id_1 )
1555
+ async with raw_data_store .db_wrapper .reader () as reader :
1556
+ result = await reader .execute ("SELECT * FROM node" )
1557
+ nodes = await result .fetchall ()
1558
+
1559
+ keys = {node ["key" ] for node in nodes if node ["key" ] is not None }
1560
+ # Since one store got all its keys deleted, we're left only with the keys of the other store.
1561
+ assert len (keys ) == total_keys_per_store
1562
+ assert keys .intersection (unique_keys_1 ) == set ()
1563
+ assert keys .symmetric_difference (common_keys .union (unique_keys_2 )) == set ()
1564
+
1565
+
1566
+ @pytest .mark .anyio
1567
+ async def test_delete_store_data_protects_pending_roots (raw_data_store : DataStore ) -> None :
1568
+ num_stores = 5
1569
+ total_keys = 15
1570
+ tree_ids = [bytes32 (i .to_bytes (32 , byteorder = "big" )) for i in range (num_stores )]
1571
+ for tree_id in tree_ids :
1572
+ await raw_data_store .create_tree (tree_id = tree_id , status = Status .COMMITTED )
1573
+ original_keys = [key .to_bytes (4 , byteorder = "big" ) for key in range (total_keys )]
1574
+ batches = []
1575
+ keys_per_pending_root = 2
1576
+
1577
+ for i in range (num_stores - 1 ):
1578
+ start_index = i * keys_per_pending_root
1579
+ end_index = (i + 1 ) * keys_per_pending_root
1580
+ batch = [{"action" : "insert" , "key" : key , "value" : key } for key in original_keys [start_index :end_index ]]
1581
+ batches .append (batch )
1582
+ for tree_id , batch in zip (tree_ids , batches ):
1583
+ await raw_data_store .insert_batch (tree_id , batch , status = Status .PENDING )
1584
+
1585
+ tree_id = tree_ids [- 1 ]
1586
+ batch = [{"action" : "insert" , "key" : key , "value" : key } for key in original_keys ]
1587
+ await raw_data_store .insert_batch (tree_id , batch , status = Status .COMMITTED )
1588
+
1589
+ async with raw_data_store .db_wrapper .reader () as reader :
1590
+ result = await reader .execute ("SELECT * FROM node" )
1591
+ nodes = await result .fetchall ()
1592
+
1593
+ keys = {node ["key" ] for node in nodes if node ["key" ] is not None }
1594
+ assert keys == set (original_keys )
1595
+
1596
+ await raw_data_store .delete_store_data (tree_id )
1597
+ async with raw_data_store .db_wrapper .reader () as reader :
1598
+ result = await reader .execute ("SELECT * FROM node" )
1599
+ nodes = await result .fetchall ()
1600
+
1601
+ keys = {node ["key" ] for node in nodes if node ["key" ] is not None }
1602
+ assert keys == set (original_keys [: (num_stores - 1 ) * keys_per_pending_root ])
1603
+
1604
+ for index in range (num_stores - 1 ):
1605
+ tree_id = tree_ids [index ]
1606
+ root = await raw_data_store .get_pending_root (tree_id )
1607
+ assert root is not None
1608
+ await raw_data_store .change_root_status (root , Status .COMMITTED )
1609
+ kv = await raw_data_store .get_keys_values (tree_id = tree_id )
1610
+ start_index = index * keys_per_pending_root
1611
+ end_index = (index + 1 ) * keys_per_pending_root
1612
+ assert {pair .key for pair in kv } == set (original_keys [start_index :end_index ])
0 commit comments