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

221 lines
5.9 KiB
Go

package hscan
import (
"math"
"strconv"
"testing"
"time"
. "github.com/bsm/ginkgo/v2"
. "github.com/bsm/gomega"
"github.com/hanzokv/go/v9/internal/util"
)
type data struct {
Omit string `kv:"-"`
Empty string
String string `kv:"string"`
Bytes []byte `kv:"byte"`
Int int `kv:"int"`
Int8 int8 `kv:"int8"`
Int16 int16 `kv:"int16"`
Int32 int32 `kv:"int32"`
Int64 int64 `kv:"int64"`
Uint uint `kv:"uint"`
Uint8 uint8 `kv:"uint8"`
Uint16 uint16 `kv:"uint16"`
Uint32 uint32 `kv:"uint32"`
Uint64 uint64 `kv:"uint64"`
Float float32 `kv:"float"`
Float64 float64 `kv:"float64"`
Bool bool `kv:"bool"`
BoolRef *bool `kv:"boolRef"`
}
type TimeRFC3339Nano struct {
time.Time
}
func (t *TimeRFC3339Nano) ScanKV(s string) (err error) {
t.Time, err = time.Parse(time.RFC3339Nano, s)
return
}
type TimeData struct {
Name string `kv:"name"`
Time *TimeRFC3339Nano `kv:"login"`
}
type i []interface{}
func TestGinkgoSuite(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "hscan")
}
var _ = Describe("Scan", func() {
It("catches bad args", func() {
var d data
Expect(Scan(&d, i{}, i{})).NotTo(HaveOccurred())
Expect(d).To(Equal(data{}))
Expect(Scan(&d, i{"key"}, i{})).To(HaveOccurred())
Expect(Scan(&d, i{"key"}, i{"1", "2"})).To(HaveOccurred())
Expect(Scan(nil, i{"key", "1"}, i{})).To(HaveOccurred())
var m map[string]interface{}
Expect(Scan(&m, i{"key"}, i{"1"})).To(HaveOccurred())
Expect(Scan(data{}, i{"key"}, i{"1"})).To(HaveOccurred())
Expect(Scan(data{}, i{"key", "string"}, i{nil, nil})).To(HaveOccurred())
})
It("number out of range", func() {
f := func(v uint64) string {
return strconv.FormatUint(v, 10) + "1"
}
keys := i{"int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float", "float64"}
vals := i{
f(math.MaxInt8), f(math.MaxInt16), f(math.MaxInt32), f(math.MaxInt64),
f(math.MaxUint8), f(math.MaxUint16), f(math.MaxUint32), strconv.FormatUint(math.MaxUint64, 10) + "1",
"13.4028234663852886e+38", "11.79769313486231570e+308",
}
for k, v := range keys {
var d data
Expect(Scan(&d, i{v}, i{vals[k]})).To(HaveOccurred())
}
// success
f = func(v uint64) string {
return strconv.FormatUint(v, 10)
}
keys = i{"int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float", "float64"}
vals = i{
f(math.MaxInt8), f(math.MaxInt16), f(math.MaxInt32), f(math.MaxInt64),
f(math.MaxUint8), f(math.MaxUint16), f(math.MaxUint32), strconv.FormatUint(math.MaxUint64, 10),
"3.40282346638528859811704183484516925440e+38", "1.797693134862315708145274237317043567981e+308",
}
var d data
Expect(Scan(&d, keys, vals)).NotTo(HaveOccurred())
Expect(d).To(Equal(data{
Int8: math.MaxInt8,
Int16: math.MaxInt16,
Int32: math.MaxInt32,
Int64: math.MaxInt64,
Uint8: math.MaxUint8,
Uint16: math.MaxUint16,
Uint32: math.MaxUint32,
Uint64: math.MaxUint64,
Float: math.MaxFloat32,
Float64: math.MaxFloat64,
}))
})
It("scans good values", func() {
var d data
// non-tagged fields.
Expect(Scan(&d, i{"key"}, i{"value"})).NotTo(HaveOccurred())
Expect(d).To(Equal(data{}))
keys := i{"string", "byte", "int", "int64", "uint", "uint64", "float", "float64", "bool", "boolRef"}
vals := i{
"str!", "bytes!", "123", "123456789123456789", "456", "987654321987654321",
"123.456", "123456789123456789.987654321987654321", "1", "1",
}
Expect(Scan(&d, keys, vals)).NotTo(HaveOccurred())
Expect(d).To(Equal(data{
String: "str!",
Bytes: []byte("bytes!"),
Int: 123,
Int64: 123456789123456789,
Uint: 456,
Uint64: 987654321987654321,
Float: 123.456,
Float64: 1.2345678912345678e+17,
Bool: true,
BoolRef: util.ToPtr(true),
}))
// Scan a different type with the same values to test that
// the struct spec maps don't conflict.
type data2 struct {
String string `kv:"string"`
Bytes []byte `kv:"byte"`
Int int `kv:"int"`
Uint uint `kv:"uint"`
Float float32 `kv:"float"`
Bool bool `kv:"bool"`
}
var d2 data2
Expect(Scan(&d2, keys, vals)).NotTo(HaveOccurred())
Expect(d2).To(Equal(data2{
String: "str!",
Bytes: []byte("bytes!"),
Int: 123,
Uint: 456,
Float: 123.456,
Bool: true,
}))
Expect(Scan(&d, i{"string", "float", "bool"}, i{"", "1", "t"})).NotTo(HaveOccurred())
Expect(d).To(Equal(data{
String: "",
Bytes: []byte("bytes!"),
Int: 123,
Int64: 123456789123456789,
Uint: 456,
Uint64: 987654321987654321,
Float: 1.0,
Float64: 1.2345678912345678e+17,
Bool: true,
BoolRef: util.ToPtr(true),
}))
})
It("omits untagged fields", func() {
var d data
Expect(Scan(&d, i{"empty", "omit", "string"}, i{"value", "value", "str!"})).NotTo(HaveOccurred())
Expect(d).To(Equal(data{
String: "str!",
}))
})
It("catches bad values", func() {
var d data
Expect(Scan(&d, i{"int"}, i{"a"})).To(HaveOccurred())
Expect(Scan(&d, i{"uint"}, i{"a"})).To(HaveOccurred())
Expect(Scan(&d, i{"uint"}, i{""})).To(HaveOccurred())
Expect(Scan(&d, i{"float"}, i{"b"})).To(HaveOccurred())
Expect(Scan(&d, i{"bool"}, i{"-1"})).To(HaveOccurred())
Expect(Scan(&d, i{"bool"}, i{""})).To(HaveOccurred())
Expect(Scan(&d, i{"bool"}, i{"123"})).To(HaveOccurred())
})
It("Implements Scanner", func() {
var td TimeData
now := time.Now()
Expect(Scan(&td, i{"name", "login"}, i{"hello", now.Format(time.RFC3339Nano)})).NotTo(HaveOccurred())
Expect(td.Name).To(Equal("hello"))
Expect(td.Time.UnixNano()).To(Equal(now.UnixNano()))
Expect(td.Time.Format(time.RFC3339Nano)).To(Equal(now.Format(time.RFC3339Nano)))
})
It("should time.Time RFC3339Nano", func() {
type TimeTime struct {
Time time.Time `kv:"time"`
}
now := time.Now()
var tt TimeTime
Expect(Scan(&tt, i{"time"}, i{now.Format(time.RFC3339Nano)})).NotTo(HaveOccurred())
Expect(now.Unix()).To(Equal(tt.Time.Unix()))
})
})