ipfs-search/ipfs-search

View on GitHub
components/index/redis/index.go

Summary

Maintainability
A
0 mins
Test Coverage
package redis

import (
    "context"
    "log"

    "github.com/ipfs-search/ipfs-search/components/index"

    radix "github.com/mediocregopher/radix/v4"
    "github.com/mediocregopher/radix/v4/resp/resp3"
)

const debug bool = false

// Index stores properties as JSON in Redis.
type Index struct {
    cfg *Config
    c   *Client
}

// New returns a new index.
func New(client *Client, cfg *Config) index.Index {
    if client == nil {
        panic("client cannot be nil")
    }

    if cfg == nil {
        panic("cfg cannot be nil")
    }

    if cfg.Name == "" {
        panic("Name cannot be empty")
    }

    if cfg.Prefix == "" {
        panic("Prefix cannot be empty")
    }

    index := &Index{
        c:   client,
        cfg: cfg,
    }

    return index
}

func (i *Index) getKey(key string) string {
    return i.c.cfg.Prefix + i.cfg.Prefix + ":" + key
}

func (i *Index) set(ctx context.Context, id string, properties interface{}) error {
    key := i.getKey(id)
    args := []string{key}

    flattened, err := resp3.Flatten(properties, nil)
    if err != nil {
        return err
    }

    if len(flattened) == 0 {
        panic("Redis cannot index without properties.")
    }

    if debug {
        log.Printf("redis %s: writing to %s", i, key)
    }

    args = append(args, flattened...)

    action := radix.Cmd(nil, "HSET", args...)
    return i.c.radixClient.Do(ctx, action)
}

// String returns the name of the index, for convenient logging.
func (i *Index) String() string {
    return i.cfg.Name
}

// Index a document's properties, identified by id
func (i *Index) Index(ctx context.Context, id string, properties interface{}) error {
    ctx, span := i.c.Tracer.Start(ctx, "index.redis.Index")
    defer span.End()

    return i.set(ctx, id, properties)
}

// Update a document's properties, given id
func (i *Index) Update(ctx context.Context, id string, properties interface{}) error {
    ctx, span := i.c.Tracer.Start(ctx, "index.redis.Update")
    defer span.End()

    return i.set(ctx, id, properties)
}

// Delete item from index
func (i *Index) Delete(ctx context.Context, id string) error {
    ctx, span := i.c.Tracer.Start(ctx, "index.redis.Delete")
    defer span.End()

    key := i.getKey(id)

    if debug {
        log.Printf("redis %s: delete %s", i, key)
    }

    // Non-blocking DEL-equivalent
    action := radix.Cmd(nil, "UNLINK", key)
    return i.c.radixClient.Do(ctx, action)
}

// Get *all* fields from document with `id` from the index, ignoring the 'fields' parameters.
//
// Returs:
// - (true, decoding_error) if found (decoding error set when errors in json)
// - (false, nil) when not found
// - (false, error) otherwise
func (i *Index) Get(ctx context.Context, id string, dst interface{}, fields ...string) (bool, error) {
    ctx, span := i.c.Tracer.Start(ctx, "index.redis.Get")
    defer span.End()

    key := i.getKey(id)

    // Wrap receiver so we can determine whether we're found or not.
    mb := &radix.Maybe{Rcv: dst}
    action := radix.Cmd(mb, "HGETALL", key)
    err := i.c.radixClient.Do(ctx, action)

    found := !(mb.Null || mb.Empty)
    if debug {
        log.Printf("redis %s: get %s from %s, res: %v, err: %v", i, id, key, found, err)
    }

    return err == nil && found, err
}

// Compile-time assurance that implementation satisfies interface.
var _ index.Index = &Index{}