Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Sorted.Create.Union #18

Merged
merged 1 commit into from
Dec 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,7 @@ func Test_Client_Single_Sorted_Create_Order(t *testing.T) {

var cli redigo.Interface
{
c := redigo.Config{
Kind: redigo.KindSingle,
}

cli, err = redigo.New(c)
if err != nil {
t.Fatal(err)
}

err = cli.Purge()
if err != nil {
t.Fatal(err)
}
cli = prgAll(redigo.Default())
}

{
Expand Down Expand Up @@ -91,95 +79,210 @@ func Test_Client_Single_Sorted_Create_Score(t *testing.T) {

var cli redigo.Interface
{
c := redigo.Config{
Kind: redigo.KindSingle,
}

cli, err = redigo.New(c)
if err != nil {
t.Fatal(err)
}

err = cli.Purge()
if err != nil {
t.Fatal(err)
}
cli = prgAll(redigo.Default())
}

{
err := cli.Sorted().Create().Index("ssk", "foo", 0.8, "a", "b")
err = cli.Sorted().Create().Index("ssk", "foo", 0.8, "a", "b")
if err != nil {
t.Fatal(err)
}
}

{
err := cli.Sorted().Create().Index("ssk", "bar", 0.7, "c", "d")
err = cli.Sorted().Create().Index("ssk", "bar", 0.7, "c", "d")
if err != nil {
t.Fatal(err)
}
}

{
err := cli.Sorted().Create().Index("ssk", "zap", 0.8, "e", "f")
err = cli.Sorted().Create().Index("ssk", "zap", 0.8, "e", "f")
if !sorted.IsAlreadyExistsError(err) {
t.Fatal("expected", "alreadyExistsError", "got", err)
}
}

{
err := cli.Sorted().Create().Index("ssk", "foo", 0.8, "g", "h")
err = cli.Sorted().Create().Index("ssk", "foo", 0.8, "g", "h")
if !sorted.IsAlreadyExistsError(err) {
t.Fatal("expected", "alreadyExistsError", "got", err)
}
}
}

func Test_Client_Single_Sorted_Create_Value(t *testing.T) {
func Test_Client_Single_Sorted_Create_Union(t *testing.T) {
var err error

var cli redigo.Interface
{
c := redigo.Config{
Kind: redigo.KindSingle,
cli = prgAll(redigo.Default())
}

{
res, err := cli.Sorted().Search().Union("k1", "k2")
if err != nil {
t.Fatal(err)
}
if len(res) != 0 {
t.Fatal("expected", 0, "got", len(res))
}
}

cli, err = redigo.New(c)
{
err = cli.Sorted().Create().Index("k1", "v3", 0.3)
if err != nil {
t.Fatal(err)
}
err = cli.Sorted().Create().Index("k1", "v4", 0.4)
if err != nil {
t.Fatal(err)
}
err = cli.Sorted().Create().Index("k1", "v5", 0.5)
if err != nil {
t.Fatal(err)
}
err = cli.Sorted().Create().Index("k1", "v6", 0.6)
if err != nil {
t.Fatal(err)
}
}

err = cli.Purge()
{
res, err := cli.Sorted().Search().Union("k1")
if err != nil {
t.Fatal(err)
}
if len(res) != 4 {
t.Fatal("expected", 4, "got", len(res))
}
if res[0] != "v3" {
t.Fatal("expected", "v3", "got", res[0])
}
if res[1] != "v4" {
t.Fatal("expected", "v4", "got", res[1])
}
if res[2] != "v5" {
t.Fatal("expected", "v5", "got", res[2])
}
if res[3] != "v6" {
t.Fatal("expected", "v6", "got", res[3])
}
}

{
err := cli.Sorted().Create().Score("ssk", "foo", 0.8)
res, err := cli.Sorted().Search().Union("k1", "k2")
if err != nil {
t.Fatal(err)
}
if len(res) != 4 {
t.Fatal("expected", 4, "got", len(res))
}
if res[0] != "v3" {
t.Fatal("expected", "v3", "got", res[0])
}
if res[1] != "v4" {
t.Fatal("expected", "v4", "got", res[1])
}
if res[2] != "v5" {
t.Fatal("expected", "v5", "got", res[2])
}
if res[3] != "v6" {
t.Fatal("expected", "v6", "got", res[3])
}
}

{
err := cli.Sorted().Create().Score("ssk", "bar", 0.7)
err = cli.Sorted().Create().Index("k2", "v2", 0.2)
if err != nil {
t.Fatal(err)
}
err = cli.Sorted().Create().Index("k2", "v4", 0.4)
if err != nil {
t.Fatal(err)
}
err = cli.Sorted().Create().Index("k2", "v5", 0.5)
if err != nil {
t.Fatal(err)
}
err = cli.Sorted().Create().Index("k2", "v7", 0.7)
if err != nil {
t.Fatal(err)
}
}

{
cou, err := cli.Sorted().Create().Union("k3", "k1", "k2")
if err != nil {
t.Fatal(err)
}
if cou != 6 {
t.Fatal("expected", 6, "got", cou)
}
}

{
res, err := cli.Sorted().Search().Order("k3", 0, -1)
if err != nil {
t.Fatal(err)
}
if len(res) != 6 {
t.Fatal("expected", 6, "got", len(res))
}
if res[0] != "v2" {
t.Fatal("expected", "v2", "got", res[0])
}
if res[1] != "v3" {
t.Fatal("expected", "v3", "got", res[1])
}
if res[2] != "v4" {
t.Fatal("expected", "v4", "got", res[2])
}
if res[3] != "v5" {
t.Fatal("expected", "v5", "got", res[3])
}
if res[4] != "v6" {
t.Fatal("expected", "v6", "got", res[4])
}
if res[5] != "v7" {
t.Fatal("expected", "v7", "got", res[5])
}
}
}

func Test_Client_Single_Sorted_Create_Value(t *testing.T) {
var err error

var cli redigo.Interface
{
cli = prgAll(redigo.Default())
}

{
err = cli.Sorted().Create().Score("ssk", "foo", 0.8)
if err != nil {
t.Fatal(err)
}
}

{
err = cli.Sorted().Create().Score("ssk", "bar", 0.7)
if err != nil {
t.Fatal(err)
}
}

// Verify we can create elements with duplicated scores.
{
err := cli.Sorted().Create().Score("ssk", "zap", 0.8)
err = cli.Sorted().Create().Score("ssk", "zap", 0.8)
if err != nil {
t.Fatal(err)
}
}

// Verify values must be unique after all.
{
err := cli.Sorted().Create().Score("ssk", "foo", 0.8)
err = cli.Sorted().Create().Score("ssk", "foo", 0.8)
if !sorted.IsAlreadyExistsError(err) {
t.Fatal("expected", "alreadyExistsError", "got", err)
}
Expand Down Expand Up @@ -208,7 +311,7 @@ func Test_Client_Single_Sorted_Create_Value(t *testing.T) {
// score. When foo is deleted, which has score 0.8 then zap must still exist
// with the same score as we verify in the next step.
{
err := cli.Sorted().Delete().Index("ssk", "foo")
err = cli.Sorted().Delete().Index("ssk", "foo")
if err != nil {
t.Fatal(err)
}
Expand Down
21 changes: 21 additions & 0 deletions conformance/single_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//go:build single

package conformance

import (
"github.com/xh3b4sd/redigo"
"github.com/xh3b4sd/tracer"
)

// prgAll is a convenience function for calling FLUSHALL. The provided redigo
// interface is returned as is.
func prgAll(red redigo.Interface) redigo.Interface {
{
err := red.Purge()
if err != nil {
tracer.Panic(tracer.Mask(err))
}
}

return red
}
9 changes: 9 additions & 0 deletions pkg/sorted/create/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package create
type Fake struct {
FakeIndex func() error
FakeScore func() error
FakeUnion func() (int64, error)
}

func (f *Fake) Index(key string, val string, sco float64, ind ...string) error {
Expand All @@ -20,3 +21,11 @@ func (f *Fake) Score(key string, val string, sco float64) error {

return nil
}

func (f *Fake) Union(dst string, key ...string) (int64, error) {
if f.FakeUnion != nil {
return f.FakeUnion()
}

return 0, nil
}
45 changes: 38 additions & 7 deletions pkg/sorted/create/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ func (r *Redis) Index(key string, val string, sco float64, ind ...string) error

if len(ind) != 0 {
m := map[string]int{}
for _, s := range ind {
m[s] = m[s] + 1
for _, x := range ind {
m[x] = m[x] + 1
}

for _, v := range m {
Expand All @@ -49,11 +49,11 @@ func (r *Redis) Index(key string, val string, sco float64, ind ...string) error
}
}

for _, s := range ind {
if s == "" {
for _, x := range ind {
if x == "" {
return tracer.Maskf(executionFailedError, "index must not be empty")
}
if strings.Count(s, " ") != 0 {
if strings.Count(x, " ") != 0 {
return tracer.Maskf(executionFailedError, "index must not contain whitespace")
}
}
Expand All @@ -66,8 +66,8 @@ func (r *Redis) Index(key string, val string, sco float64, ind ...string) error
arg = append(arg, val) // ARGV[1]
arg = append(arg, sco) // ARGV[2]

for _, s := range ind {
arg = append(arg, s)
for _, x := range ind {
arg = append(arg, x)
}
}

Expand Down Expand Up @@ -117,3 +117,34 @@ func (r *Redis) Score(key string, val string, sco float64) error {

return nil
}

func (r *Redis) Union(dst string, key ...string) (int64, error) {
var err error

var con redis.Conn
{
con = r.poo.Get()
defer con.Close()
}

var arg []interface{}
{
arg = append(arg, dst, len(key))

for _, x := range key {
arg = append(arg, prefix.WithKeys(r.pre, x))
}

arg = append(arg, "AGGREGATE", "MIN")
}

var res int64
{
res, err = redis.Int64(con.Do("ZUNIONSTORE", arg...))
if err != nil {
return 0, tracer.Mask(err)
}
}

return res, nil
}
Loading