<commit_before>package redtable

import (
	"errors"
	"fmt"
	"strconv"
	"sync"

	"github.com/garyburd/redigo/redis"
)

const (
	opMulti   = "MULTI"
	opHGet    = "HGET"
	opHMGet   = "HMGET"
	opHDel    = "HDEL"
	opHSet    = "HSET"
	opHKeys   = "HKEYS"
	opHExists = "HEXISTS"
	opExec    = "EXEC"
	opDel     = "DEL"

	EnvRedisServerURL = "REDIS_SERVER_URL"
)

var (
	ErrConnectionAlreadyClosed = errors.New("connection already closed")
)

type Client struct {
	shutdownMu    sync.Mutex
	alreadyClosed bool
	// TODO: (@odeke-em) decide if we should have a pool of connections
	// instead of using one connection or different dials per operation.
	conn redis.Conn
}

var errExpectingAURL = fmt.Errorf("expecting at least one URL")

func New(redisServerURLs ...string) (*Client, error) {
	var conn redis.Conn
	var err error = errExpectingAURL
	for _, srvURL := range redisServerURLs {
		conn, err = redis.DialURL(srvURL)

		if err != nil {
			continue
		}

		client := &Client{conn: conn}
		return client, nil
	}

	return nil, err
}

func (c *Client) Close() error {
	c.shutdownMu.Lock()
	defer c.shutdownMu.Unlock()
	if c.alreadyClosed {
		return ErrConnectionAlreadyClosed
	}

	if err := c.conn.Close(); err != nil {
		return err
	}

	c.alreadyClosed = true
	return nil
}

func (c *Client) doHashOp(opName, hashTableName string, args ...interface{}) ([]interface{}, error) {
	if c.alreadyClosed {
		return nil, ErrConnectionAlreadyClosed
	}

	if err := c.conn.Send(opMulti); err != nil {
		return nil, err
	}

	allArgs := append([]interface{}{hashTableName}, args...)
	if sendErr := c.conn.Send(opName, allArgs...); sendErr != nil {
		return nil, sendErr
	}

	return redis.Values(c.conn.Do(opExec))
}

func (c *Client) HSet(hashTableName string, key, value interface{}) (interface{}, error) {
	replies, err := c.doHashOp(opHSet, hashTableName, key, value)
	if err != nil {
		return nil, err
	}
	return replies[0], nil
}

func multiKeysOp(c *Client, opName, hashTableName string, keys ...interface{}) ([]interface{}, error) {
	return c.doHashOp(opName, hashTableName, keys...)
}

func byKeyOp(c *Client, opName, hashTableName string, keys ...interface{}) (interface{}, error) {
	replies, err := multiKeysOp(c, opName, hashTableName, keys)
	if err != nil {
		return nil, err
	}
	return replies[0], nil
}

func (c *Client) HGet(hashTableName string, key interface{}) (interface{}, error) {
	return byKeyOp(c, opHGet, hashTableName, key)
}

func (c *Client) HMGet(hashTableName string, keys ...interface{}) ([]interface{}, error) {
	return c.doHashOp(opHMGet, hashTableName, keys...)
}

func (c *Client) HDel(hashTableName string, key interface{}) (interface{}, error) {
	return byKeyOp(c, opHDel, hashTableName, key)
}

// HPop performs a pop which is a combination of `HGet` and `HDel` from a HashTable
func (c *Client) HPop(hashTableName string, key interface{}) (interface{}, error) {
	// HPop is a combination of HGet+HDel, in that order
	retrieved, err := c.HGet(hashTableName, key)<commit_msg>multiKeyOp: fix unintended conversion from []interface{} to interface{}<commit_after>package redtable

import (
	"errors"
	"fmt"
	"strconv"
	"sync"

	"github.com/garyburd/redigo/redis"
)

const (
	opMulti   = "MULTI"
	opHGet    = "HGET"
	opHMGet   = "HMGET"
	opHDel    = "HDEL"
	opHSet    = "HSET"
	opHKeys   = "HKEYS"
	opHExists = "HEXISTS"
	opExec    = "EXEC"
	opDel     = "DEL"

	EnvRedisServerURL = "REDIS_SERVER_URL"
)

var (
	ErrConnectionAlreadyClosed = errors.New("connection already closed")
)

type Client struct {
	shutdownMu    sync.Mutex
	alreadyClosed bool
	// TODO: (@odeke-em) decide if we should have a pool of connections
	// instead of using one connection or different dials per operation.
	conn redis.Conn
}

var errExpectingAURL = fmt.Errorf("expecting at least one URL")

func New(redisServerURLs ...string) (*Client, error) {
	var conn redis.Conn
	var err error = errExpectingAURL
	for _, srvURL := range redisServerURLs {
		conn, err = redis.DialURL(srvURL)

		if err != nil {
			continue
		}

		client := &Client{conn: conn}
		return client, nil
	}

	return nil, err
}

func (c *Client) Close() error {
	c.shutdownMu.Lock()
	defer c.shutdownMu.Unlock()
	if c.alreadyClosed {
		return ErrConnectionAlreadyClosed
	}

	if err := c.conn.Close(); err != nil {
		return err
	}

	c.alreadyClosed = true
	return nil
}

func (c *Client) doHashOp(opName, hashTableName string, args ...interface{}) ([]interface{}, error) {
	if c.alreadyClosed {
		return nil, ErrConnectionAlreadyClosed
	}

	if err := c.conn.Send(opMulti); err != nil {
		return nil, err
	}

	allArgs := append([]interface{}{hashTableName}, args...)
	if sendErr := c.conn.Send(opName, allArgs...); sendErr != nil {
		return nil, sendErr
	}
	return redis.Values(c.conn.Do(opExec))
}

func (c *Client) HSet(hashTableName string, key, value interface{}) (interface{}, error) {
	replies, err := c.doHashOp(opHSet, hashTableName, key, value)
	if err != nil {
		return nil, err
	}
	return replies[0], nil
}

func multiKeysOp(c *Client, opName, hashTableName string, keys ...interface{}) ([]interface{}, error) {
	return c.doHashOp(opName, hashTableName, keys...)
}

func byKeyOp(c *Client, opName, hashTableName string, keys ...interface{}) (interface{}, error) {
	replies, err := multiKeysOp(c, opName, hashTableName, keys...)
	if err != nil {
		return nil, err
	}
	return replies[0], nil
}

func (c *Client) HGet(hashTableName string, key interface{}) (interface{}, error) {
	return byKeyOp(c, opHGet, hashTableName, key)
}

func (c *Client) HMGet(hashTableName string, keys ...interface{}) ([]interface{}, error) {
	return c.doHashOp(opHMGet, hashTableName, keys...)
}

func (c *Client) HDel(hashTableName string, key interface{}) (interface{}, error) {
	return byKeyOp(c, opHDel, hashTableName, key)
}

// HPop performs a pop which is a combination of `HGet` and `HDel` from a HashTable
func (c *Client) HPop(hashTableName string, key interface{}) (interface{}, error) {
	// HPop is a combination of HGet+HDel, in that order
	retrieved, err := c.HGet(hashTableName, key)
{'fraction_non_alphanumeric': 0.13841059602649006, 'fraction_numerical': 0.0006622516556291391, 'mean_word_length': 4.1281833616298815, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5843234', 'n_tokens_mistral': 2217, 'n_tokens_neox': 2122, 'n_words': 616}