Skip to content

Latest commit

 

History

History
 
 

om

Generic Object Mapping

The om.NewHashRepository and om.NewJSONRepository creates an OM repository backed by valkey hash or RedisJSON.

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/valkey-io/valkey-go"
    "github.com/valkey-io/valkey-go/om"
)

type Example struct {
    Key  string    `json:"key" valkey:",key"`   // the valkey:",key" is required to indicate which field is the ULID key
    Ver  int64     `json:"ver" valkey:",ver"`   // the valkey:",ver" is required to do optimistic locking to prevent lost update
    ExAt time.Time `json:"exat" valkey:",exat"` // the valkey:",exat" is optional for setting record expiry with unix timestamp
    Str  string    `json:"str"`                // both NewHashRepository and NewJSONRepository use json tag as field name
}

func main() {
    ctx := context.Background()
    c, err := valkey.NewClient(valkey.ClientOption{InitAddress: []string{"127.0.0.1:6379"}})
    if err != nil {
        panic(err)
    }
    // create the repo with NewHashRepository or NewJSONRepository
    repo := om.NewHashRepository("my_prefix", Example{}, c)

    exp := repo.NewEntity()
    exp.Str = "mystr"
    exp.ExAt = time.Now().Add(time.Hour)
    fmt.Println(exp.Key) // output 01FNH4FCXV9JTB9WTVFAAKGSYB
    repo.Save(ctx, exp) // success

    // lookup "my_prefix:01FNH4FCXV9JTB9WTVFAAKGSYB" through client side caching
    exp2, _ := repo.FetchCache(ctx, exp.Key, time.Second*5)
    fmt.Println(exp2.Str) // output "mystr", which equals to exp.Str

    exp2.Ver = 0         // if someone changes the version during your GET then SET operation,
    repo.Save(ctx, exp2) // the save will fail with ErrVersionMismatch.
}

Object Mapping + RediSearch

If you have RediSearch, you can create and search the repository against the index.

if _, ok := repo.(*om.HashRepository[Example]); ok {
    repo.CreateIndex(ctx, func(schema om.FtCreateSchema) valkey.Completed {
        return schema.FieldName("str").Tag().Build() // Note that the Example.Str field is mapped to str on valkey by its json tag
    })
}

if _, ok := repo.(*om.JSONRepository[Example]); ok {
    repo.CreateIndex(ctx, func(schema om.FtCreateSchema) valkey.Completed {
        return schema.FieldName("$.str").As("str").Tag().Build() // the FieldName of a json index should be a json path syntax
    })
}

exp := repo.NewEntity()
exp.Str = "special_chars:[$.-]"
repo.Save(ctx, exp)

n, records, _ := repo.Search(ctx, func(search om.FtSearchIndex) valkey.Completed {
    // Note that by using the named parameters with DIALECT >= 2, you won't have to escape chars for building queries.
    return search.Query("@str:{$v}").Params().Nargs(2).NameValue().NameValue("v", exp.Str).Dialect(2).Build()
})

fmt.Println("total", n) // n is total number of results matched in valkey, which is >= len(records)

for _, v := range records {
    fmt.Println(v.Str) // print "special_chars:[$.-]"
}

Change Search Index Name

The default index name for HashRepository and JSONRepository is hashidx:{prefix} and jsonidx:{prefix} respectively.

They can be changed by WithIndexName option to allow searching difference indexes:

repo1 := om.NewHashRepository("my_prefix", Example{}, c, om.WithIndexName("my_index1"))
repo2 := om.NewHashRepository("my_prefix", Example{}, c, om.WithIndexName("my_index2"))

Object Expiry Timestamp

Setting a valkey:",exat" tag on a time.Time field will set PEXPIREAT on the record accordingly when calling .Save().

If the time.Time is zero, then the expiry will be untouched when calling .Save().

Object Mapping Limitation

NewHashRepository only accepts these field types:

  • string, *string
  • int64, *int64
  • bool, *bool
  • []byte, json.RawMessage
  • []float32, []float64 for vector search
  • json.Marshaler+json.Unmarshaler

Field projection by RediSearch is not supported.