@@ -521,12 +521,33 @@ public GarnetStatus DELETE<TContext, TObjectContext>(byte[] key, StoreType store
521
521
}
522
522
523
523
public unsafe GarnetStatus RENAME( ArgSlice oldKeySlice , ArgSlice newKeySlice , StoreType storeType )
524
+ {
525
+ return RENAME( oldKeySlice , newKeySlice , storeType , false , out _ ) ;
526
+ }
527
+
528
+ /// <summary>
529
+ /// Renames key to newkey if newkey does not yet exist. It returns an error when key does not exist.
530
+ /// </summary>
531
+ /// <param name="oldKeySlice">The old key to be renamed.</param>
532
+ /// <param name="newKeySlice">The new key name.</param>
533
+ /// <param name="storeType">The type of store to perform the operation on.</param>
534
+ /// <returns></returns>
535
+ public unsafe GarnetStatus RENAMENX( ArgSlice oldKeySlice , ArgSlice newKeySlice , StoreType storeType , out int result )
536
+ {
537
+ return RENAME( oldKeySlice , newKeySlice , storeType , true , out result ) ;
538
+ }
539
+
540
+ private unsafe GarnetStatus RENAME( ArgSlice oldKeySlice , ArgSlice newKeySlice , StoreType storeType , bool isNX , out int result )
524
541
{
525
542
GarnetStatus returnStatus = GarnetStatus. NOTFOUND ;
543
+ result = - 1 ;
526
544
527
545
// If same name check return early.
528
546
if ( oldKeySlice . ReadOnlySpan . SequenceEqual ( newKeySlice . ReadOnlySpan ) )
547
+ {
548
+ result = 1 ;
529
549
return GarnetStatus. OK ;
550
+ }
530
551
531
552
bool createTransaction = false ;
532
553
if ( txnManager . state != TxnState . Running )
@@ -570,23 +591,68 @@ public unsafe GarnetStatus RENAME(ArgSlice oldKeySlice, ArgSlice newKeySlice, St
570
591
// If the key has an expiration, set the new key with the expiration
571
592
if ( expireTimeMs > 0 )
572
593
{
573
- SETEX ( newKeySlice , new ArgSlice ( ptrVal , headerLength ) , TimeSpan . FromMilliseconds ( expireTimeMs ) , ref context ) ;
594
+ if ( isNX )
595
+ {
596
+ // Move payload forward to make space for RespInputHeader and Metadata
597
+ var setValue = scratchBufferManager. FormatScratch ( RespInputHeader . Size + sizeof ( long ) , new ArgSlice ( ptrVal , headerLength ) ) ;
598
+ var setValueSpan = setValue. SpanByte ;
599
+ var setValuePtr = setValueSpan . ToPointerWithMetadata ( ) ;
600
+ setValueSpan. ExtraMetadata = DateTimeOffset . UtcNow . Ticks + TimeSpan . FromMilliseconds ( expireTimeMs ) . Ticks ;
601
+ ( ( RespInputHeader * ) ( setValuePtr + sizeof ( long ) ) ) ->cmd = RespCommand . SETEXNX ;
602
+ ( ( RespInputHeader * ) ( setValuePtr + sizeof ( long ) ) ) ->flags = 0 ;
603
+ var newKey = newKeySlice. SpanByte;
604
+ var setStatus = SET_Conditional ( ref newKey , ref setValueSpan , ref context ) ;
605
+
606
+ // For SET NX `NOTFOUND` means the operation succeeded
607
+ result = setStatus == GarnetStatus . NOTFOUND ? 1 : 0 ;
608
+ returnStatus = GarnetStatus . OK ;
609
+ }
610
+ else
611
+ {
612
+ SETEX ( newKeySlice , new ArgSlice ( ptrVal , headerLength ) , TimeSpan . FromMilliseconds ( expireTimeMs ) , ref context ) ;
613
+ }
574
614
}
575
615
else if ( expireTimeMs == - 1 ) // Its possible to have expireTimeMs as 0 (Key expired or will be expired now) or -2 (Key does not exist), in those cases we don't SET the new key
576
616
{
577
- SpanByte newKey = newKeySlice. SpanByte;
578
- var value = SpanByte . FromPinnedPointer ( ptrVal , headerLength ) ;
579
- SET ( ref newKey , ref value , ref context ) ;
617
+ if ( isNX )
618
+ {
619
+ // Move payload forward to make space for RespInputHeader
620
+ var setValue = scratchBufferManager. FormatScratch ( RespInputHeader . Size , new ArgSlice ( ptrVal , headerLength ) ) ;
621
+ var setValueSpan = setValue. SpanByte ;
622
+ var setValuePtr = setValueSpan . ToPointerWithMetadata ( ) ;
623
+ ( ( RespInputHeader * ) setValuePtr ) ->cmd = RespCommand . SETEXNX ;
624
+ ( ( RespInputHeader * ) setValuePtr ) ->flags = 0 ;
625
+ var newKey = newKeySlice. SpanByte;
626
+ var setStatus = SET_Conditional( ref newKey , ref setValueSpan , ref context ) ;
627
+
628
+ // For SET NX `NOTFOUND` means the operation succeeded
629
+ result = setStatus == GarnetStatus . NOTFOUND ? 1 : 0 ;
630
+ returnStatus = GarnetStatus . OK ;
631
+ }
632
+ else
633
+ {
634
+ SpanByte newKey = newKeySlice. SpanByte;
635
+ var value = SpanByte . FromPinnedPointer ( ptrVal , headerLength ) ;
636
+ SET ( ref newKey , ref value , ref context ) ;
637
+ }
580
638
}
581
639
582
640
expireSpan . Memory . Dispose ( ) ;
583
641
memoryHandle . Dispose ( ) ;
584
642
o . Memory . Dispose ( ) ;
585
643
586
- // Delete the old key
587
- DELETE ( ref oldKey , StoreType . Main , ref context , ref objectContext ) ;
644
+ // Delete the old key only when SET NX succeeded
645
+ if ( isNX && result == 1 )
646
+ {
647
+ DELETE ( ref oldKey , StoreType . Main , ref context , ref objectContext ) ;
648
+ }
649
+ else if ( ! isNX )
650
+ {
651
+ // Delete the old key
652
+ DELETE ( ref oldKey , StoreType . Main , ref context , ref objectContext ) ;
588
653
589
- returnStatus = GarnetStatus . OK ;
654
+ returnStatus = GarnetStatus . OK ;
655
+ }
590
656
}
591
657
}
592
658
}
@@ -618,32 +684,29 @@ public unsafe GarnetStatus RENAME(ArgSlice oldKeySlice, ArgSlice newKeySlice, St
618
684
var valObj = value . garnetObject ;
619
685
byte [ ] newKeyArray = newKeySlice . ToArray ( ) ;
620
686
621
- var expireSpan = new SpanByteAndMemory ( ) ;
622
- var ttlStatus = TTL ( ref oldKey , StoreType . Object , ref expireSpan , ref context , ref objectContext , true ) ;
623
-
624
- if ( ttlStatus = = GarnetStatus . OK && ! expireSpan . IsSpanByte )
687
+ returnStatus = GarnetStatus . OK ;
688
+ var canSetAndDelete = true;
689
+ if ( isNX )
625
690
{
626
- using var expireMemoryHandle = expireSpan . Memory . Memory . Pin ( ) ;
627
- var expirePtrVal = ( byte * ) expireMemoryHandle . Pointer ;
628
- RespReadUtils . TryRead64Int ( out var expireTimeMs , ref expirePtrVal , expirePtrVal + expireSpan . Length , out var _ ) ;
629
- expireSpan . Memory . Dispose ( ) ;
691
+ // Not using EXISTS method to avoid new allocation of Array for key
692
+ var getNewStatus = GET ( newKeyArray , out _ , ref objectContext ) ;
693
+ canSetAndDelete = getNewStatus == GarnetStatus . NOTFOUND ;
694
+ }
630
695
631
- if ( expireTimeMs > 0 )
632
- {
633
- SET ( newKeyArray , valObj , ref objectContext ) ;
634
- EXPIRE ( newKeySlice , TimeSpan . FromMilliseconds ( expireTimeMs ) , out _ , StoreType . Object , ExpireOption . None , ref context , ref objectContext , true ) ;
635
- }
636
- else if ( expireTimeMs = = - 1 ) // Its possible to have expire as 0 or -2, in those cases we don't SET the new key
637
- {
638
- SET ( newKeyArray , valObj , ref objectContext ) ;
639
- }
696
+ if ( canSetAndDelete )
697
+ {
698
+ // valObj already has expiration time, so no need to write expiration logic here
699
+ SET ( newKeyArray , valObj , ref objectContext ) ;
640
700
641
701
// Delete the old key
642
702
DELETE ( oldKeyArray , StoreType . Object , ref context , ref objectContext ) ;
643
703
644
- returnStatus = GarnetStatus . OK ;
704
+ result = 1 ;
705
+ }
706
+ else
707
+ {
708
+ result = 0 ;
645
709
}
646
-
647
710
}
648
711
}
649
712
finally
@@ -652,7 +715,6 @@ public unsafe GarnetStatus RENAME(ArgSlice oldKeySlice, ArgSlice newKeySlice, St
652
715
txnManager. Commit ( true ) ;
653
716
}
654
717
}
655
-
656
718
return returnStatus;
657
719
}
658
720
0 commit comments