mirror of
https://github.com/hanzokv/go.git
synced 2026-09-24 00:31:51 +00:00
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.
183 lines
3.6 KiB
Go
183 lines
3.6 KiB
Go
// EXAMPLE: pipe_trans_tutorial
|
|
// HIDE_START
|
|
package example_commands_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/hanzokv/go/v9"
|
|
)
|
|
|
|
// HIDE_END
|
|
|
|
func ExampleClient_transactions() {
|
|
ctx := context.Background()
|
|
|
|
rdb := kv.NewClient(&kv.Options{
|
|
Addr: "localhost:6379",
|
|
Password: "", // no password docs
|
|
DB: 0, // use default DB
|
|
})
|
|
// REMOVE_START
|
|
// make sure we are working with fresh database
|
|
rdb.FlushDB(ctx)
|
|
for i := 0; i < 5; i++ {
|
|
rdb.Del(ctx, fmt.Sprintf("seat:%d", i))
|
|
}
|
|
|
|
rdb.Del(ctx, "counter:1", "counter:2", "counter:3", "shellpath")
|
|
// REMOVE_END
|
|
|
|
// STEP_START basic_pipe
|
|
pipe := rdb.Pipeline()
|
|
|
|
for i := 0; i < 5; i++ {
|
|
pipe.Set(ctx, fmt.Sprintf("seat:%v", i), fmt.Sprintf("#%v", i), 0)
|
|
}
|
|
|
|
cmds, err := pipe.Exec(ctx)
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
for _, c := range cmds {
|
|
fmt.Printf("%v;", c.(*kv.StatusCmd).Val())
|
|
}
|
|
|
|
fmt.Println("")
|
|
// >>> OK;OK;OK;OK;OK;
|
|
|
|
pipe = rdb.Pipeline()
|
|
|
|
get0Result := pipe.Get(ctx, "seat:0")
|
|
get3Result := pipe.Get(ctx, "seat:3")
|
|
get4Result := pipe.Get(ctx, "seat:4")
|
|
|
|
cmds, err = pipe.Exec(ctx)
|
|
|
|
// The results are available only after the pipeline
|
|
// has finished executing.
|
|
fmt.Println(get0Result.Val()) // >>> #0
|
|
fmt.Println(get3Result.Val()) // >>> #3
|
|
fmt.Println(get4Result.Val()) // >>> #4
|
|
// STEP_END
|
|
|
|
// STEP_START basic_pipe_pipelined
|
|
var pd0Result *kv.StatusCmd
|
|
var pd3Result *kv.StatusCmd
|
|
var pd4Result *kv.StatusCmd
|
|
|
|
cmds, err = rdb.Pipelined(ctx, func(pipe kv.Pipeliner) error {
|
|
pd0Result = (*kv.StatusCmd)(pipe.Get(ctx, "seat:0"))
|
|
pd3Result = (*kv.StatusCmd)(pipe.Get(ctx, "seat:3"))
|
|
pd4Result = (*kv.StatusCmd)(pipe.Get(ctx, "seat:4"))
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// The results are available only after the pipeline
|
|
// has finished executing.
|
|
fmt.Println(pd0Result.Val()) // >>> #0
|
|
fmt.Println(pd3Result.Val()) // >>> #3
|
|
fmt.Println(pd4Result.Val()) // >>> #4
|
|
// STEP_END
|
|
|
|
// STEP_START basic_trans
|
|
trans := rdb.TxPipeline()
|
|
|
|
trans.IncrBy(ctx, "counter:1", 1)
|
|
trans.IncrBy(ctx, "counter:2", 2)
|
|
trans.IncrBy(ctx, "counter:3", 3)
|
|
|
|
cmds, err = trans.Exec(ctx)
|
|
|
|
for _, c := range cmds {
|
|
fmt.Println(c.(*kv.IntCmd).Val())
|
|
}
|
|
// >>> 1
|
|
// >>> 2
|
|
// >>> 3
|
|
// STEP_END
|
|
|
|
// STEP_START basic_trans_txpipelined
|
|
var tx1Result *kv.IntCmd
|
|
var tx2Result *kv.IntCmd
|
|
var tx3Result *kv.IntCmd
|
|
|
|
cmds, err = rdb.TxPipelined(ctx, func(trans kv.Pipeliner) error {
|
|
tx1Result = trans.IncrBy(ctx, "counter:1", 1)
|
|
tx2Result = trans.IncrBy(ctx, "counter:2", 2)
|
|
tx3Result = trans.IncrBy(ctx, "counter:3", 3)
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
fmt.Println(tx1Result.Val()) // >>> 2
|
|
fmt.Println(tx2Result.Val()) // >>> 4
|
|
fmt.Println(tx3Result.Val()) // >>> 6
|
|
// STEP_END
|
|
|
|
// STEP_START trans_watch
|
|
// Set initial value of `shellpath`.
|
|
rdb.Set(ctx, "shellpath", "/usr/syscmds/", 0)
|
|
|
|
const maxRetries = 1000
|
|
|
|
// Retry if the key has been changed.
|
|
for i := 0; i < maxRetries; i++ {
|
|
err := rdb.Watch(ctx,
|
|
func(tx *kv.Tx) error {
|
|
currentPath, err := rdb.Get(ctx, "shellpath").Result()
|
|
newPath := currentPath + ":/usr/mycmds/"
|
|
|
|
_, err = tx.TxPipelined(ctx, func(pipe kv.Pipeliner) error {
|
|
pipe.Set(ctx, "shellpath", newPath, 0)
|
|
return nil
|
|
})
|
|
|
|
return err
|
|
},
|
|
"shellpath",
|
|
)
|
|
|
|
if err == nil {
|
|
// Success.
|
|
break
|
|
} else if err == kv.TxFailedErr {
|
|
// Optimistic lock lost. Retry the transaction.
|
|
continue
|
|
} else {
|
|
// Panic for any other error.
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
fmt.Println(rdb.Get(ctx, "shellpath").Val())
|
|
// >>> /usr/syscmds/:/usr/mycmds/
|
|
// STEP_END
|
|
|
|
// Output:
|
|
// OK;OK;OK;OK;OK;
|
|
// #0
|
|
// #3
|
|
// #4
|
|
// #0
|
|
// #3
|
|
// #4
|
|
// 1
|
|
// 2
|
|
// 3
|
|
// 2
|
|
// 4
|
|
// 6
|
|
// /usr/syscmds/:/usr/mycmds/
|
|
}
|