@@ -1462,3 +1462,259 @@ async fn test_multiple_secondary_indexes() {
14621462 . unwrap ( ) ;
14631463 client. shutdown ( ) . await . unwrap ( ) ;
14641464}
1465+
1466+ // ============================================================
1467+ // List and scan partial ranges
1468+ // ============================================================
1469+
1470+ #[ tokio:: test]
1471+ async fn test_list_partial_range ( ) {
1472+ let ( _container, address) = start_oxia ( ) . await ;
1473+ let client = new_client ( & address) . await ;
1474+
1475+ for key in & [ "pr/a" , "pr/b" , "pr/c" , "pr/d" , "pr/e" ] {
1476+ client. put ( key. to_string ( ) , b"v" . to_vec ( ) ) . await . unwrap ( ) ;
1477+ }
1478+
1479+ // List only a subset of the range
1480+ let result = client
1481+ . list ( "pr/b" . to_string ( ) , "pr/d" . to_string ( ) )
1482+ . await
1483+ . unwrap ( ) ;
1484+ assert_eq ! ( result. keys. len( ) , 2 ) ;
1485+ assert ! ( result. keys. contains( & "pr/b" . to_string( ) ) ) ;
1486+ assert ! ( result. keys. contains( & "pr/c" . to_string( ) ) ) ;
1487+
1488+ client
1489+ . delete_range ( "pr/" . to_string ( ) , "pr/~" . to_string ( ) )
1490+ . await
1491+ . unwrap ( ) ;
1492+ client. shutdown ( ) . await . unwrap ( ) ;
1493+ }
1494+
1495+ // ============================================================
1496+ // Put and immediately get - verify consistency
1497+ // ============================================================
1498+
1499+ #[ tokio:: test]
1500+ async fn test_read_your_writes ( ) {
1501+ let ( _container, address) = start_oxia ( ) . await ;
1502+ let client = new_client ( & address) . await ;
1503+
1504+ for i in 0 ..50 {
1505+ let key = format ! ( "ryw/{}" , i) ;
1506+ let value = format ! ( "value-{}" , i) ;
1507+ client
1508+ . put ( key. clone ( ) , value. clone ( ) . into_bytes ( ) )
1509+ . await
1510+ . unwrap ( ) ;
1511+ let result = client. get ( key) . await . unwrap ( ) ;
1512+ assert_eq ! ( result. value, Some ( value. into_bytes( ) ) ) ;
1513+ }
1514+
1515+ client
1516+ . delete_range ( "ryw/" . to_string ( ) , "ryw/~" . to_string ( ) )
1517+ . await
1518+ . unwrap ( ) ;
1519+ client. shutdown ( ) . await . unwrap ( ) ;
1520+ }
1521+
1522+ // ============================================================
1523+ // Multiple clients operating concurrently
1524+ // ============================================================
1525+
1526+ #[ tokio:: test]
1527+ async fn test_multiple_clients ( ) {
1528+ let ( _container, address) = start_oxia ( ) . await ;
1529+ let client1 = new_client ( & address) . await ;
1530+ let client2 = new_client ( & address) . await ;
1531+
1532+ // Client 1 writes
1533+ client1
1534+ . put ( "mc/key1" . to_string ( ) , b"from-client-1" . to_vec ( ) )
1535+ . await
1536+ . unwrap ( ) ;
1537+
1538+ // Client 2 reads
1539+ let result = client2. get ( "mc/key1" . to_string ( ) ) . await . unwrap ( ) ;
1540+ assert_eq ! ( result. value, Some ( b"from-client-1" . to_vec( ) ) ) ;
1541+
1542+ // Client 2 writes
1543+ client2
1544+ . put ( "mc/key2" . to_string ( ) , b"from-client-2" . to_vec ( ) )
1545+ . await
1546+ . unwrap ( ) ;
1547+
1548+ // Client 1 reads
1549+ let result = client1. get ( "mc/key2" . to_string ( ) ) . await . unwrap ( ) ;
1550+ assert_eq ! ( result. value, Some ( b"from-client-2" . to_vec( ) ) ) ;
1551+
1552+ client1
1553+ . delete_range ( "mc/" . to_string ( ) , "mc/~" . to_string ( ) )
1554+ . await
1555+ . unwrap ( ) ;
1556+ client1. shutdown ( ) . await . unwrap ( ) ;
1557+ client2. shutdown ( ) . await . unwrap ( ) ;
1558+ }
1559+
1560+ // ============================================================
1561+ // CAS (Compare-And-Swap) loop pattern
1562+ // ============================================================
1563+
1564+ #[ tokio:: test]
1565+ async fn test_cas_loop ( ) {
1566+ let ( _container, address) = start_oxia ( ) . await ;
1567+ let client = new_client ( & address) . await ;
1568+
1569+ // Create initial record
1570+ let initial = client
1571+ . put ( "cas/counter" . to_string ( ) , b"0" . to_vec ( ) )
1572+ . await
1573+ . unwrap ( ) ;
1574+
1575+ // Simulate a CAS loop: read-modify-write with version check
1576+ let mut current_version = initial. version . version_id ;
1577+ for i in 1 ..=5 {
1578+ let result = client
1579+ . put_with_options (
1580+ "cas/counter" . to_string ( ) ,
1581+ format ! ( "{}" , i) . into_bytes ( ) ,
1582+ vec ! [ PutOption :: ExpectVersionId ( current_version) ] ,
1583+ )
1584+ . await
1585+ . unwrap ( ) ;
1586+ current_version = result. version . version_id ;
1587+ }
1588+
1589+ // Verify final value
1590+ let final_result = client. get ( "cas/counter" . to_string ( ) ) . await . unwrap ( ) ;
1591+ assert_eq ! ( final_result. value, Some ( b"5" . to_vec( ) ) ) ;
1592+
1593+ client
1594+ . delete_range ( "cas/" . to_string ( ) , "cas/~" . to_string ( ) )
1595+ . await
1596+ . unwrap ( ) ;
1597+ client. shutdown ( ) . await . unwrap ( ) ;
1598+ }
1599+
1600+ // ============================================================
1601+ // Binary (non-UTF8) data
1602+ // ============================================================
1603+
1604+ #[ tokio:: test]
1605+ async fn test_binary_values ( ) {
1606+ let ( _container, address) = start_oxia ( ) . await ;
1607+ let client = new_client ( & address) . await ;
1608+
1609+ // Store binary data that isn't valid UTF-8
1610+ let binary_data: Vec < u8 > = vec ! [ 0x00 , 0x01 , 0xFF , 0xFE , 0x80 , 0x90 , 0xAB , 0xCD ] ;
1611+ client
1612+ . put ( "bin/data" . to_string ( ) , binary_data. clone ( ) )
1613+ . await
1614+ . unwrap ( ) ;
1615+
1616+ let result = client. get ( "bin/data" . to_string ( ) ) . await . unwrap ( ) ;
1617+ assert_eq ! ( result. value, Some ( binary_data) ) ;
1618+
1619+ client
1620+ . delete_range ( "bin/" . to_string ( ) , "bin/~" . to_string ( ) )
1621+ . await
1622+ . unwrap ( ) ;
1623+ client. shutdown ( ) . await . unwrap ( ) ;
1624+ }
1625+
1626+ // ============================================================
1627+ // Overwrite with different value sizes
1628+ // ============================================================
1629+
1630+ #[ tokio:: test]
1631+ async fn test_overwrite_different_sizes ( ) {
1632+ let ( _container, address) = start_oxia ( ) . await ;
1633+ let client = new_client ( & address) . await ;
1634+
1635+ // Start with a small value
1636+ client
1637+ . put ( "sizes/key" . to_string ( ) , b"small" . to_vec ( ) )
1638+ . await
1639+ . unwrap ( ) ;
1640+
1641+ // Overwrite with a larger value
1642+ let large_value = vec ! [ b'x' ; 10000 ] ;
1643+ client
1644+ . put ( "sizes/key" . to_string ( ) , large_value. clone ( ) )
1645+ . await
1646+ . unwrap ( ) ;
1647+
1648+ let result = client. get ( "sizes/key" . to_string ( ) ) . await . unwrap ( ) ;
1649+ assert_eq ! ( result. value, Some ( large_value) ) ;
1650+
1651+ // Overwrite back to a small value
1652+ client
1653+ . put ( "sizes/key" . to_string ( ) , b"tiny" . to_vec ( ) )
1654+ . await
1655+ . unwrap ( ) ;
1656+
1657+ let result = client. get ( "sizes/key" . to_string ( ) ) . await . unwrap ( ) ;
1658+ assert_eq ! ( result. value, Some ( b"tiny" . to_vec( ) ) ) ;
1659+
1660+ client
1661+ . delete_range ( "sizes/" . to_string ( ) , "sizes/~" . to_string ( ) )
1662+ . await
1663+ . unwrap ( ) ;
1664+ client. shutdown ( ) . await . unwrap ( ) ;
1665+ }
1666+
1667+ // ============================================================
1668+ // Concurrent CAS conflict detection
1669+ // ============================================================
1670+
1671+ #[ tokio:: test]
1672+ async fn test_concurrent_cas_conflict ( ) {
1673+ let ( _container, address) = start_oxia ( ) . await ;
1674+ let client = new_client ( & address) . await ;
1675+
1676+ // Create a key
1677+ let initial = client
1678+ . put ( "conflict/key" . to_string ( ) , b"v0" . to_vec ( ) )
1679+ . await
1680+ . unwrap ( ) ;
1681+ let version = initial. version . version_id ;
1682+
1683+ // Two concurrent CAS updates with the same version - one should fail
1684+ let c1 = client. clone ( ) ;
1685+ let c2 = client. clone ( ) ;
1686+
1687+ let h1 = tokio:: spawn ( async move {
1688+ c1. put_with_options (
1689+ "conflict/key" . to_string ( ) ,
1690+ b"v1-from-c1" . to_vec ( ) ,
1691+ vec ! [ PutOption :: ExpectVersionId ( version) ] ,
1692+ )
1693+ . await
1694+ } ) ;
1695+
1696+ let h2 = tokio:: spawn ( async move {
1697+ c2. put_with_options (
1698+ "conflict/key" . to_string ( ) ,
1699+ b"v1-from-c2" . to_vec ( ) ,
1700+ vec ! [ PutOption :: ExpectVersionId ( version) ] ,
1701+ )
1702+ . await
1703+ } ) ;
1704+
1705+ let r1 = h1. await . unwrap ( ) ;
1706+ let r2 = h2. await . unwrap ( ) ;
1707+
1708+ // Exactly one should succeed and one should fail with UnexpectedVersionId
1709+ let ( successes, failures) : ( Vec < _ > , Vec < _ > ) = vec ! [ r1, r2] . into_iter ( ) . partition ( |r| r. is_ok ( ) ) ;
1710+
1711+ assert_eq ! ( successes. len( ) , 1 , "Exactly one CAS should succeed" ) ;
1712+ assert_eq ! ( failures. len( ) , 1 , "Exactly one CAS should fail" ) ;
1713+ assert ! ( matches!( failures[ 0 ] , Err ( OxiaError :: UnexpectedVersionId ( ) ) ) ) ;
1714+
1715+ client
1716+ . delete_range ( "conflict/" . to_string ( ) , "conflict/~" . to_string ( ) )
1717+ . await
1718+ . unwrap ( ) ;
1719+ client. shutdown ( ) . await . unwrap ( ) ;
1720+ }
0 commit comments