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.
334 lines
6.1 KiB
Go
334 lines
6.1 KiB
Go
// EXAMPLE: go_home_json
|
|
// HIDE_START
|
|
package example_commands_test
|
|
|
|
// HIDE_END
|
|
// STEP_START import
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/hanzokv/go/v9"
|
|
)
|
|
|
|
// STEP_END
|
|
|
|
func ExampleClient_search_json() {
|
|
// STEP_START connect
|
|
ctx := context.Background()
|
|
|
|
rdb := kv.NewClient(&kv.Options{
|
|
Addr: "localhost:6379",
|
|
Password: "", // no password docs
|
|
DB: 0, // use default DB
|
|
Protocol: 2,
|
|
})
|
|
// STEP_END
|
|
// REMOVE_START
|
|
// make sure we are working with fresh database
|
|
rdb.FlushDB(ctx)
|
|
rdb.Del(ctx, "user:1", "user:2", "user:3")
|
|
rdb.FTDropIndex(ctx, "idx:users")
|
|
// REMOVE_END
|
|
|
|
// STEP_START create_data
|
|
user1 := map[string]interface{}{
|
|
"name": "Paul John",
|
|
"email": "[email protected]",
|
|
"age": 42,
|
|
"city": "London",
|
|
}
|
|
|
|
user2 := map[string]interface{}{
|
|
"name": "Eden Zamir",
|
|
"email": "[email protected]",
|
|
"age": 29,
|
|
"city": "Tel Aviv",
|
|
}
|
|
|
|
user3 := map[string]interface{}{
|
|
"name": "Paul Zamir",
|
|
"email": "[email protected]",
|
|
"age": 35,
|
|
"city": "Tel Aviv",
|
|
}
|
|
// STEP_END
|
|
|
|
// STEP_START make_index
|
|
_, err := rdb.FTCreate(
|
|
ctx,
|
|
"idx:users",
|
|
// Options:
|
|
&kv.FTCreateOptions{
|
|
OnJSON: true,
|
|
Prefix: []interface{}{"user:"},
|
|
},
|
|
// Index schema fields:
|
|
&kv.FieldSchema{
|
|
FieldName: "$.name",
|
|
As: "name",
|
|
FieldType: kv.SearchFieldTypeText,
|
|
},
|
|
&kv.FieldSchema{
|
|
FieldName: "$.city",
|
|
As: "city",
|
|
FieldType: kv.SearchFieldTypeTag,
|
|
},
|
|
&kv.FieldSchema{
|
|
FieldName: "$.age",
|
|
As: "age",
|
|
FieldType: kv.SearchFieldTypeNumeric,
|
|
},
|
|
).Result()
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
// STEP_END
|
|
|
|
// STEP_START add_data
|
|
_, err = rdb.JSONSet(ctx, "user:1", "$", user1).Result()
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
_, err = rdb.JSONSet(ctx, "user:2", "$", user2).Result()
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
_, err = rdb.JSONSet(ctx, "user:3", "$", user3).Result()
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
// STEP_END
|
|
|
|
// STEP_START query1
|
|
findPaulResult, err := rdb.FTSearch(
|
|
ctx,
|
|
"idx:users",
|
|
"Paul @age:[30 40]",
|
|
).Result()
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
fmt.Println(findPaulResult)
|
|
// >>> {1 [{user:3 <nil> <nil> <nil> map[$:{"age":35,"city":"Tel Aviv"...
|
|
// STEP_END
|
|
|
|
// STEP_START query2
|
|
citiesResult, err := rdb.FTSearchWithArgs(
|
|
ctx,
|
|
"idx:users",
|
|
"Paul",
|
|
&kv.FTSearchOptions{
|
|
Return: []kv.FTSearchReturn{
|
|
{
|
|
FieldName: "$.city",
|
|
As: "city",
|
|
},
|
|
},
|
|
},
|
|
).Result()
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
sort.Slice(citiesResult.Docs, func(i, j int) bool {
|
|
return citiesResult.Docs[i].Fields["city"] < citiesResult.Docs[j].Fields["city"]
|
|
})
|
|
|
|
for _, result := range citiesResult.Docs {
|
|
fmt.Println(result.Fields["city"])
|
|
}
|
|
// >>> London
|
|
// >>> Tel Aviv
|
|
// STEP_END
|
|
|
|
// STEP_START query2count_only
|
|
citiesResult2, err := rdb.FTSearchWithArgs(
|
|
ctx,
|
|
"idx:users",
|
|
"Paul",
|
|
&kv.FTSearchOptions{
|
|
Return: []kv.FTSearchReturn{
|
|
{
|
|
FieldName: "$.city",
|
|
As: "city",
|
|
},
|
|
},
|
|
CountOnly: true,
|
|
},
|
|
).Result()
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// The `Total` field has the correct number of docs found
|
|
// by the query but the `Docs` slice is empty.
|
|
fmt.Println(len(citiesResult2.Docs)) // >>> 0
|
|
fmt.Println(citiesResult2.Total) // >>> 2
|
|
// STEP_END
|
|
|
|
// STEP_START query3
|
|
aggOptions := kv.FTAggregateOptions{
|
|
GroupBy: []kv.FTAggregateGroupBy{
|
|
{
|
|
Fields: []interface{}{"@city"},
|
|
Reduce: []kv.FTAggregateReducer{
|
|
{
|
|
Reducer: kv.SearchCount,
|
|
As: "count",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
aggResult, err := rdb.FTAggregateWithArgs(
|
|
ctx,
|
|
"idx:users",
|
|
"*",
|
|
&aggOptions,
|
|
).Result()
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
sort.Slice(aggResult.Rows, func(i, j int) bool {
|
|
return aggResult.Rows[i].Fields["city"].(string) <
|
|
aggResult.Rows[j].Fields["city"].(string)
|
|
})
|
|
|
|
for _, row := range aggResult.Rows {
|
|
fmt.Printf("%v - %v\n",
|
|
row.Fields["city"], row.Fields["count"],
|
|
)
|
|
}
|
|
// >>> City: London - 1
|
|
// >>> City: Tel Aviv - 2
|
|
// STEP_END
|
|
|
|
// Output:
|
|
// {1 [{user:3 <nil> <nil> <nil> map[$:{"age":35,"city":"Tel Aviv","email":"[email protected]","name":"Paul Zamir"}] <nil>}]}
|
|
// London
|
|
// Tel Aviv
|
|
// 0
|
|
// 2
|
|
// London - 1
|
|
// Tel Aviv - 2
|
|
}
|
|
|
|
func ExampleClient_search_hash() {
|
|
ctx := context.Background()
|
|
|
|
rdb := kv.NewClient(&kv.Options{
|
|
Addr: "localhost:6379",
|
|
Password: "", // no password docs
|
|
DB: 0, // use default DB
|
|
Protocol: 2,
|
|
})
|
|
|
|
// REMOVE_START
|
|
rdb.Del(ctx, "huser:1", "huser:2", "huser:3")
|
|
rdb.FTDropIndex(ctx, "hash-idx:users")
|
|
// REMOVE_END
|
|
|
|
// STEP_START make_hash_index
|
|
_, err := rdb.FTCreate(
|
|
ctx,
|
|
"hash-idx:users",
|
|
// Options:
|
|
&kv.FTCreateOptions{
|
|
OnHash: true,
|
|
Prefix: []interface{}{"huser:"},
|
|
},
|
|
// Index schema fields:
|
|
&kv.FieldSchema{
|
|
FieldName: "name",
|
|
FieldType: kv.SearchFieldTypeText,
|
|
},
|
|
&kv.FieldSchema{
|
|
FieldName: "city",
|
|
FieldType: kv.SearchFieldTypeTag,
|
|
},
|
|
&kv.FieldSchema{
|
|
FieldName: "age",
|
|
FieldType: kv.SearchFieldTypeNumeric,
|
|
},
|
|
).Result()
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
// STEP_END
|
|
|
|
user1 := map[string]interface{}{
|
|
"name": "Paul John",
|
|
"email": "[email protected]",
|
|
"age": 42,
|
|
"city": "London",
|
|
}
|
|
|
|
user2 := map[string]interface{}{
|
|
"name": "Eden Zamir",
|
|
"email": "[email protected]",
|
|
"age": 29,
|
|
"city": "Tel Aviv",
|
|
}
|
|
|
|
user3 := map[string]interface{}{
|
|
"name": "Paul Zamir",
|
|
"email": "[email protected]",
|
|
"age": 35,
|
|
"city": "Tel Aviv",
|
|
}
|
|
|
|
// STEP_START add_hash_data
|
|
_, err = rdb.HSet(ctx, "huser:1", user1).Result()
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
_, err = rdb.HSet(ctx, "huser:2", user2).Result()
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
_, err = rdb.HSet(ctx, "huser:3", user3).Result()
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
// STEP_END
|
|
|
|
// STEP_START query1_hash
|
|
findPaulHashResult, err := rdb.FTSearch(
|
|
ctx,
|
|
"hash-idx:users",
|
|
"Paul @age:[30 40]",
|
|
).Result()
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
fmt.Println(findPaulHashResult)
|
|
// >>> {1 [{huser:3 <nil> <nil> <nil> map[age:35 city:Tel Aviv...
|
|
// STEP_END
|
|
|
|
// Output:
|
|
// {1 [{huser:3 <nil> <nil> <nil> map[age:35 city:Tel Aviv email:[email protected] name:Paul Zamir] <nil>}]}
|
|
}
|