Files
zeekay 6994cf05aa kv, not redis: rename the package, the extras and the URL scheme
We do not use Redis. The client was a go-redis fork and still said so
everywhere: `package redis`, `redis.NewClient`, `redis://`, `extra/redisotel`.
Consumers wrote `redis.Client` against a Valkey-backed store called KV.

  package redis            -> package kv          (93 decls)
  redis.X                  -> kv.X                (2,794 call sites)
  "redis: …" errors        -> "kv: …"             (206)
  extra/redis{otel,cmd,prometheus,census} -> extra/kv{otel,cmd,prometheus,census}
  redis:// , rediss://     -> kv:// , kvs://      (ParseURL)
  Redis*/‌*Redis* idents    -> KV*/*KV*
  REDIS_VERSION, REDIS_ENDPOINTS_CONFIG_PATH -> KV_*

I previously argued this rename was unsafe because much of it was "wire
contract". That was wrong, and the categorisation shows why: RESP command names
are GET/SET/HSET — they never contain the word. Of 5,771 occurrences only two
kinds were server-visible at all: the ParseURL scheme (we choose what we pass;
our configs already use kv://) and the CLIENT SETINFO lib-name label, which the
server merely records. Nothing here changes a byte of protocol.

Two things deliberately keep the old spelling:
  RediSearch  — names a specific external module the FT.* commands map to.
  StoreDist   — contains the letters by accident (sto-reDis-t), not the word.

Breaking for consumers by design: every import site must move redis.X -> kv.X.
Minor bump, never a major — this module stays v9 forever.
2026-07-26 08:34:19 -07:00

245 lines
7.1 KiB
Go

package main
import (
"context"
"fmt"
"time"
"github.com/hanzokv/go/v9"
"github.com/hanzokv/go/v9/helper"
)
func main() {
ctx := context.Background()
// Connect to KV
rdb := kv.NewClient(&kv.Options{
Addr: "localhost:6379",
})
defer rdb.Close()
// Ping to verify connection
if err := rdb.Ping(ctx).Err(); err != nil {
fmt.Printf("Failed to connect to KV: %v\n", err)
return
}
fmt.Println("=== KV Digest & Optimistic Locking Example ===")
fmt.Println()
// Example 1: Basic Digest Usage
fmt.Println("1. Basic Digest Usage")
fmt.Println("---------------------")
basicDigestExample(ctx, rdb)
fmt.Println()
// Example 2: Optimistic Locking with SetIFDEQ
fmt.Println("2. Optimistic Locking with SetIFDEQ")
fmt.Println("------------------------------------")
optimisticLockingExample(ctx, rdb)
fmt.Println()
// Example 3: Detecting Changes with SetIFDNE
fmt.Println("3. Detecting Changes with SetIFDNE")
fmt.Println("-----------------------------------")
detectChangesExample(ctx, rdb)
fmt.Println()
// Example 4: Conditional Delete with DelExArgs
fmt.Println("4. Conditional Delete with DelExArgs")
fmt.Println("-------------------------------------")
conditionalDeleteExample(ctx, rdb)
fmt.Println()
// Example 5: Client-Side Digest Generation
fmt.Println("5. Client-Side Digest Generation")
fmt.Println("---------------------------------")
clientSideDigestExample(ctx, rdb)
fmt.Println()
fmt.Println("=== All examples completed successfully! ===")
}
// basicDigestExample demonstrates getting a digest from KV
func basicDigestExample(ctx context.Context, rdb *kv.Client) {
// Set a value
key := "user:1000:name"
value := "Alice"
rdb.Set(ctx, key, value, 0)
// Get the digest
digest := rdb.Digest(ctx, key).Val()
fmt.Printf("Key: %s\n", key)
fmt.Printf("Value: %s\n", value)
fmt.Printf("Digest: %d (0x%016x)\n", digest, digest)
// Verify with client-side calculation
clientDigest := helper.DigestString(value)
fmt.Printf("Client-calculated digest: %d (0x%016x)\n", clientDigest, clientDigest)
if digest == clientDigest {
fmt.Println("✓ Digests match!")
}
}
// optimisticLockingExample demonstrates using SetIFDEQ for optimistic locking
func optimisticLockingExample(ctx context.Context, rdb *kv.Client) {
key := "counter"
// Initial value
rdb.Set(ctx, key, "100", 0)
fmt.Printf("Initial value: %s\n", rdb.Get(ctx, key).Val())
// Get current digest
currentDigest := rdb.Digest(ctx, key).Val()
fmt.Printf("Current digest: 0x%016x\n", currentDigest)
// Simulate some processing time
time.Sleep(100 * time.Millisecond)
// Try to update only if value hasn't changed (digest matches)
newValue := "150"
result := rdb.SetIFDEQ(ctx, key, newValue, currentDigest, 0)
if result.Err() == kv.Nil {
fmt.Println("✗ Update failed: value was modified by another client")
} else if result.Err() != nil {
fmt.Printf("✗ Error: %v\n", result.Err())
} else {
fmt.Printf("✓ Update successful! New value: %s\n", rdb.Get(ctx, key).Val())
}
// Try again with wrong digest (simulating concurrent modification)
wrongDigest := uint64(12345)
result = rdb.SetIFDEQ(ctx, key, "200", wrongDigest, 0)
if result.Err() == kv.Nil {
fmt.Println("✓ Correctly rejected update with wrong digest")
}
}
// detectChangesExample demonstrates using SetIFDNE to detect if a value changed
func detectChangesExample(ctx context.Context, rdb *kv.Client) {
key := "config:version"
// Set initial value
oldValue := "v1.0.0"
rdb.Set(ctx, key, oldValue, 0)
fmt.Printf("Initial value: %s\n", oldValue)
// Calculate digest of a DIFFERENT value (what we expect it NOT to be)
unwantedValue := "v0.9.0"
unwantedDigest := helper.DigestString(unwantedValue)
fmt.Printf("Unwanted value digest: 0x%016x\n", unwantedDigest)
// Update to new value only if current value is NOT the unwanted value
// (i.e., only if digest does NOT match unwantedDigest)
newValue := "v2.0.0"
result := rdb.SetIFDNE(ctx, key, newValue, unwantedDigest, 0)
if result.Err() == kv.Nil {
fmt.Println("✗ Current value matches unwanted value (digest matches)")
} else if result.Err() != nil {
fmt.Printf("✗ Error: %v\n", result.Err())
} else {
fmt.Printf("✓ Current value is different from unwanted value! Updated to: %s\n", rdb.Get(ctx, key).Val())
}
// Try to update again, but this time the digest matches current value (should fail)
currentDigest := rdb.Digest(ctx, key).Val()
result = rdb.SetIFDNE(ctx, key, "v3.0.0", currentDigest, 0)
if result.Err() == kv.Nil {
fmt.Println("✓ Correctly rejected: current value matches the digest (IFDNE failed)")
}
}
// conditionalDeleteExample demonstrates using DelExArgs with digest
func conditionalDeleteExample(ctx context.Context, rdb *kv.Client) {
key := "session:abc123"
value := "user_data_here"
// Set a value
rdb.Set(ctx, key, value, 0)
fmt.Printf("Created session: %s\n", key)
// Calculate expected digest
expectedDigest := helper.DigestString(value)
fmt.Printf("Expected digest: 0x%016x\n", expectedDigest)
// Try to delete with wrong digest (should fail)
wrongDigest := uint64(99999)
deleted := rdb.DelExArgs(ctx, key, kv.DelExArgs{
Mode: "IFDEQ",
MatchDigest: wrongDigest,
}).Val()
if deleted == 0 {
fmt.Println("✓ Correctly refused to delete (wrong digest)")
}
// Delete with correct digest (should succeed)
deleted = rdb.DelExArgs(ctx, key, kv.DelExArgs{
Mode: "IFDEQ",
MatchDigest: expectedDigest,
}).Val()
if deleted == 1 {
fmt.Println("✓ Successfully deleted with correct digest")
}
// Verify deletion
exists := rdb.Exists(ctx, key).Val()
if exists == 0 {
fmt.Println("✓ Session deleted")
}
}
// clientSideDigestExample demonstrates calculating digests without fetching from KV
func clientSideDigestExample(ctx context.Context, rdb *kv.Client) {
key := "product:1001:price"
// Scenario: We know the expected current value
expectedCurrentValue := "29.99"
newValue := "24.99"
// Set initial value
rdb.Set(ctx, key, expectedCurrentValue, 0)
fmt.Printf("Current price: $%s\n", expectedCurrentValue)
// Calculate digest client-side (no need to fetch from KV!)
expectedDigest := helper.DigestString(expectedCurrentValue)
fmt.Printf("Expected digest (calculated client-side): 0x%016x\n", expectedDigest)
// Update price only if it matches our expectation
result := rdb.SetIFDEQ(ctx, key, newValue, expectedDigest, 0)
if result.Err() == kv.Nil {
fmt.Println("✗ Price was already changed by someone else")
actualValue := rdb.Get(ctx, key).Val()
fmt.Printf(" Actual current price: $%s\n", actualValue)
} else if result.Err() != nil {
fmt.Printf("✗ Error: %v\n", result.Err())
} else {
fmt.Printf("✓ Price updated successfully to $%s\n", newValue)
}
// Demonstrate with binary data
fmt.Println("\nBinary data example:")
binaryKey := "image:thumbnail"
binaryData := []byte{0xFF, 0xD8, 0xFF, 0xE0} // JPEG header
rdb.Set(ctx, binaryKey, binaryData, 0)
// Calculate digest for binary data
binaryDigest := helper.DigestBytes(binaryData)
fmt.Printf("Binary data digest: 0x%016x\n", binaryDigest)
// Verify it matches KV
kvDigest := rdb.Digest(ctx, binaryKey).Val()
if binaryDigest == kvDigest {
fmt.Println("✓ Binary digest matches!")
}
}