pax_global_header00006660000000000000000000000064125311670710014515gustar00rootroot0000000000000052 comment=97bd090877ec34e2eebb75a547eaab25bf92dee4 simpleredis-2.0/000077500000000000000000000000001253116707100136765ustar00rootroot00000000000000simpleredis-2.0/.travis.yml000066400000000000000000000000721253116707100160060ustar00rootroot00000000000000language: go services: - redis-server go: - 1.4 simpleredis-2.0/LICENSE000066400000000000000000000020771253116707100147110ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 Alexander F Rødseth Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. simpleredis-2.0/README.md000066400000000000000000000042641253116707100151630ustar00rootroot00000000000000Simple Redis ============ [![Build Status](https://travis-ci.org/xyproto/simpleredis.svg?branch=master)](https://travis-ci.org/xyproto/simpleredis) [![GoDoc](https://godoc.org/github.com/xyproto/simpleredis?status.svg)](http://godoc.org/github.com/xyproto/simpleredis) Easy way to use Redis from Go. Online API Documentation ------------------------ [godoc.org](http://godoc.org/github.com/xyproto/simpleredis) Features and limitations ------------------------ * Supports simple use of lists, hashmaps, sets and key/values * Deals mainly with strings * Uses the [redigo](https://github.com/garyburd/redigo) package Example usage ------------- ~~~go package main import ( "log" "github.com/xyproto/simpleredis" ) func main() { // Check if the redis service is up if err := simpleredis.TestConnection(); err != nil { log.Fatalln("Could not connect to Redis. Is the service up and running?") } // Use instead for testing if a different host/port is up. // simpleredis.TestConnectionHost("localhost:6379") // Create a connection pool, connect to the given redis server pool := simpleredis.NewConnectionPool() // Use this for connecting to a different redis host/port // pool := simpleredis.NewConnectionPoolHost("localhost:6379") // For connecting to a different redis host/port, with a password // pool := simpleredis.NewConnectionPoolHost("password@redishost:6379") // Close the connection pool right after this function returns defer pool.Close() // Create a list named "greetings" list := simpleredis.NewList(pool, "greetings") // Add "hello" to the list, check if there are errors if list.Add("hello") != nil { log.Fatalln("Could not add an item to list!") } // Get the last item of the list if item, err := list.GetLast(); err != nil { log.Fatalln("Could not fetch the last item from the list!") } else { log.Println("The value of the stored item is:", item) } // Remove the list if list.Remove() != nil { log.Fatalln("Could not remove the list!") } } ~~~ Testing ------- Redis must be up and running locally for the `go test` tests to work. Version, license and author --------------------------- * Version: 2.0 * License: MIT * Author: Alexander F Rødseth simpleredis-2.0/creator.go000066400000000000000000000015041253116707100156640ustar00rootroot00000000000000package simpleredis import ( "github.com/xyproto/pinterface" ) // For implementing pinterface.ICreator type RedisCreator struct { pool *ConnectionPool dbindex int } func NewCreator(pool *ConnectionPool, dbindex int) *RedisCreator { return &RedisCreator{pool, dbindex} } func (c *RedisCreator) SelectDatabase(dbindex int) { c.dbindex = dbindex } func (c *RedisCreator) NewList(id string) (pinterface.IList, error) { return &List{c.pool, id, c.dbindex}, nil } func (c *RedisCreator) NewSet(id string) (pinterface.ISet, error) { return &Set{c.pool, id, c.dbindex}, nil } func (c *RedisCreator) NewHashMap(id string) (pinterface.IHashMap, error) { return &HashMap{c.pool, id, c.dbindex}, nil } func (c *RedisCreator) NewKeyValue(id string) (pinterface.IKeyValue, error) { return &KeyValue{c.pool, id, c.dbindex}, nil } simpleredis-2.0/example/000077500000000000000000000000001253116707100153315ustar00rootroot00000000000000simpleredis-2.0/example/main.go000066400000000000000000000025121253116707100166040ustar00rootroot00000000000000package main import ( "log" "github.com/xyproto/simpleredis" ) func main() { // Check if the redis service is up if err := simpleredis.TestConnection(); err != nil { log.Fatalln("Could not connect to Redis. Is the service up and running?") } // Use instead for testing if a different host/port is up. // simpleredis.TestConnectionHost("localhost:6379") // Create a connection pool, connect to the given redis server pool := simpleredis.NewConnectionPool() // Use this for connecting to a different redis host/port // pool := simpleredis.NewConnectionPoolHost("localhost:6379") // For connecting to a different redis host/port, with a password // pool := simpleredis.NewConnectionPoolHost("password@redishost:6379") // Close the connection pool right after this function returns defer pool.Close() // Create a list named "greetings" list := simpleredis.NewList(pool, "greetings") // Add "hello" to the list, check if there are errors if list.Add("hello") != nil { log.Fatalln("Could not add an item to list!") } // Get the last item of the list if item, err := list.GetLast(); err != nil { log.Fatalln("Could not fetch the last item from the list!") } else { log.Println("The value of the stored item is:", item) } // Remove the list if list.Remove() != nil { log.Fatalln("Could not remove the list!") } } simpleredis-2.0/simpleredis.go000066400000000000000000000276421253116707100165600ustar00rootroot00000000000000// Easy way to use Redis from Go. package simpleredis import ( "errors" "strconv" "strings" "github.com/garyburd/redigo/redis" ) const ( // Version number. Stable API within major version numbers. Version = 2.0 ) // Common for each of the redis datastructures used here type redisDatastructure struct { pool *ConnectionPool id string dbindex int } type ( // A pool of readily available Redis connections ConnectionPool redis.Pool List redisDatastructure Set redisDatastructure HashMap redisDatastructure KeyValue redisDatastructure ) const ( // The default [url]:port that Redis is running at defaultRedisServer = ":6379" ) var ( // How many connections should stay ready for requests, at a maximum? // When an idle connection is used, new idle connections are created. maxIdleConnections = 3 ) /* --- Helper functions --- */ // Connect to the local instance of Redis at port 6379 func newRedisConnection() (redis.Conn, error) { return newRedisConnectionTo(defaultRedisServer) } // Connect to host:port, host may be omitted, so ":6379" is valid. // Will not try to AUTH with any given password (password@host:port). func newRedisConnectionTo(hostColonPort string) (redis.Conn, error) { // Discard the password, if provided if _, theRest, ok := twoFields(hostColonPort, "@"); ok { hostColonPort = theRest } hostColonPort = strings.TrimSpace(hostColonPort) return redis.Dial("tcp", hostColonPort) } // Get a string from a list of results at a given position func getString(bi []interface{}, i int) string { return string(bi[i].([]uint8)) } // Test if the local Redis server is up and running func TestConnection() (err error) { return TestConnectionHost(defaultRedisServer) } // Test if a given Redis server at host:port is up and running. // Does not try to PING or AUTH. func TestConnectionHost(hostColonPort string) (err error) { // Connect to the given host:port conn, err := newRedisConnectionTo(hostColonPort) if conn != nil { conn.Close() } defer func() { if r := recover(); r != nil { err = errors.New("Could not connect to redis server: " + hostColonPort) } }() return err } /* --- ConnectionPool functions --- */ // Create a new connection pool func NewConnectionPool() *ConnectionPool { // The second argument is the maximum number of idle connections redisPool := redis.NewPool(newRedisConnection, maxIdleConnections) pool := ConnectionPool(*redisPool) return &pool } // Split a string into two parts, given a delimiter. // Returns the two parts and true if it works out. func twoFields(s, delim string) (string, string, bool) { if strings.Count(s, delim) != 1 { return s, "", false } fields := strings.Split(s, delim) return fields[0], fields[1], true } // Create a new connection pool given a host:port string. // A password may be supplied as well, on the form "password@host:port". func NewConnectionPoolHost(hostColonPort string) *ConnectionPool { // Create a redis Pool redisPool := redis.NewPool( // Anonymous function for calling new RedisConnectionTo with the host:port func() (redis.Conn, error) { conn, err := newRedisConnectionTo(hostColonPort) if err != nil { return nil, err } // If a password is given, use it to authenticate if password, _, ok := twoFields(hostColonPort, "@"); ok { if password != "" { if _, err := conn.Do("AUTH", password); err != nil { conn.Close() return nil, err } } } return conn, err }, // Maximum number of idle connections to the redis database maxIdleConnections) pool := ConnectionPool(*redisPool) return &pool } // Set the number of maximum *idle* connections standing ready when // creating new connection pools. When an idle connection is used, // a new idle connection is created. The default is 3 and should be fine // for most cases. func SetMaxIdleConnections(maximum int) { maxIdleConnections = maximum } // Get one of the available connections from the connection pool, given a database index func (pool *ConnectionPool) Get(dbindex int) redis.Conn { redisPool := redis.Pool(*pool) conn := redisPool.Get() // The default database index is 0 if dbindex != 0 { // SELECT is not critical, ignore the return values conn.Do("SELECT", strconv.Itoa(dbindex)) } return conn } // Ping the server by sending a PING command func (pool *ConnectionPool) Ping() error { redisPool := redis.Pool(*pool) conn := redisPool.Get() _, err := conn.Do("PING") return err } // Close down the connection pool func (pool *ConnectionPool) Close() { redisPool := redis.Pool(*pool) redisPool.Close() } /* --- List functions --- */ // Create a new list func NewList(pool *ConnectionPool, id string) *List { return &List{pool, id, 0} } // Select a different database func (rl *List) SelectDatabase(dbindex int) { rl.dbindex = dbindex } // Add an element to the list func (rl *List) Add(value string) error { conn := rl.pool.Get(rl.dbindex) _, err := conn.Do("RPUSH", rl.id, value) return err } // Get all elements of a list func (rl *List) GetAll() ([]string, error) { conn := rl.pool.Get(rl.dbindex) result, err := redis.Values(conn.Do("LRANGE", rl.id, "0", "-1")) strs := make([]string, len(result)) for i := 0; i < len(result); i++ { strs[i] = getString(result, i) } return strs, err } // Get the last element of a list func (rl *List) GetLast() (string, error) { conn := rl.pool.Get(rl.dbindex) result, err := redis.Values(conn.Do("LRANGE", rl.id, "-1", "-1")) if len(result) == 1 { return getString(result, 0), err } return "", err } // Get the last N elements of a list func (rl *List) GetLastN(n int) ([]string, error) { conn := rl.pool.Get(rl.dbindex) result, err := redis.Values(conn.Do("LRANGE", rl.id, "-"+strconv.Itoa(n), "-1")) strs := make([]string, len(result)) for i := 0; i < len(result); i++ { strs[i] = getString(result, i) } return strs, err } // Remove this list func (rl *List) Remove() error { conn := rl.pool.Get(rl.dbindex) _, err := conn.Do("DEL", rl.id) return err } // Clear the contents func (rl *List) Clear() error { return rl.Remove() } /* --- Set functions --- */ // Create a new set func NewSet(pool *ConnectionPool, id string) *Set { return &Set{pool, id, 0} } // Select a different database func (rs *Set) SelectDatabase(dbindex int) { rs.dbindex = dbindex } // Add an element to the set func (rs *Set) Add(value string) error { conn := rs.pool.Get(rs.dbindex) _, err := conn.Do("SADD", rs.id, value) return err } // Check if a given value is in the set func (rs *Set) Has(value string) (bool, error) { conn := rs.pool.Get(rs.dbindex) retval, err := conn.Do("SISMEMBER", rs.id, value) if err != nil { panic(err) } return redis.Bool(retval, err) } // Get all elements of the set func (rs *Set) GetAll() ([]string, error) { conn := rs.pool.Get(rs.dbindex) result, err := redis.Values(conn.Do("SMEMBERS", rs.id)) strs := make([]string, len(result)) for i := 0; i < len(result); i++ { strs[i] = getString(result, i) } return strs, err } // Remove an element from the set func (rs *Set) Del(value string) error { conn := rs.pool.Get(rs.dbindex) _, err := conn.Do("SREM", rs.id, value) return err } // Remove this set func (rs *Set) Remove() error { conn := rs.pool.Get(rs.dbindex) _, err := conn.Do("DEL", rs.id) return err } // Clear the contents func (rs *Set) Clear() error { return rs.Remove() } /* --- HashMap functions --- */ // Create a new hashmap func NewHashMap(pool *ConnectionPool, id string) *HashMap { return &HashMap{pool, id, 0} } // Select a different database func (rh *HashMap) SelectDatabase(dbindex int) { rh.dbindex = dbindex } // Set a value in a hashmap given the element id (for instance a user id) and the key (for instance "password") func (rh *HashMap) Set(elementid, key, value string) error { conn := rh.pool.Get(rh.dbindex) _, err := conn.Do("HSET", rh.id+":"+elementid, key, value) return err } // Get a value from a hashmap given the element id (for instance a user id) and the key (for instance "password") func (rh *HashMap) Get(elementid, key string) (string, error) { conn := rh.pool.Get(rh.dbindex) result, err := redis.String(conn.Do("HGET", rh.id+":"+elementid, key)) if err != nil { return "", err } return result, nil } // Check if a given elementid + key is in the hash map func (rh *HashMap) Has(elementid, key string) (bool, error) { conn := rh.pool.Get(rh.dbindex) retval, err := conn.Do("HEXISTS", rh.id+":"+elementid, key) if err != nil { panic(err) } return redis.Bool(retval, err) } // Check if a given elementid exists as a hash map at all func (rh *HashMap) Exists(elementid string) (bool, error) { // TODO: key is not meant to be a wildcard, check for "*" return hasKey(rh.pool, rh.id+":"+elementid, rh.dbindex) } // Get all elementid's for all hash elements func (rh *HashMap) GetAll() ([]string, error) { conn := rh.pool.Get(rh.dbindex) result, err := redis.Values(conn.Do("KEYS", rh.id+":*")) strs := make([]string, len(result)) idlen := len(rh.id) for i := 0; i < len(result); i++ { strs[i] = getString(result, i)[idlen+1:] } return strs, err } // Remove a key for an entry in a hashmap (for instance the email field for a user) func (rh *HashMap) DelKey(elementid, key string) error { conn := rh.pool.Get(rh.dbindex) _, err := conn.Do("HDEL", rh.id+":"+elementid, key) return err } // Remove an element (for instance a user) func (rh *HashMap) Del(elementid string) error { conn := rh.pool.Get(rh.dbindex) _, err := conn.Do("DEL", rh.id+":"+elementid) return err } // Remove this hashmap (all keys that starts with this hashmap id and a colon) func (rh *HashMap) Remove() error { conn := rh.pool.Get(rh.dbindex) // Find all hashmap keys that starts with rh.id+":" results, err := redis.Values(conn.Do("KEYS", rh.id+":*")) if err != nil { return err } // For each key id for i := 0; i < len(results); i++ { // Delete this key if _, err = conn.Do("DEL", getString(results, i)); err != nil { return err } } return nil } // Clear the contents func (rh *HashMap) Clear() error { return rh.Remove() } /* --- KeyValue functions --- */ // Create a new key/value func NewKeyValue(pool *ConnectionPool, id string) *KeyValue { return &KeyValue{pool, id, 0} } // Select a different database func (rkv *KeyValue) SelectDatabase(dbindex int) { rkv.dbindex = dbindex } // Set a key and value func (rkv *KeyValue) Set(key, value string) error { conn := rkv.pool.Get(rkv.dbindex) _, err := conn.Do("SET", rkv.id+":"+key, value) return err } // Get a value given a key func (rkv *KeyValue) Get(key string) (string, error) { conn := rkv.pool.Get(rkv.dbindex) result, err := redis.String(conn.Do("GET", rkv.id+":"+key)) if err != nil { return "", err } return result, nil } // Remove a key func (rkv *KeyValue) Del(key string) error { conn := rkv.pool.Get(rkv.dbindex) _, err := conn.Do("DEL", rkv.id+":"+key) return err } // Increase the value of a key, returns the new value // Returns an empty string if there were errors, // or "0" if the key does not already exist. func (rkv *KeyValue) Inc(key string) (string, error) { conn := rkv.pool.Get(rkv.dbindex) result, err := redis.Int64(conn.Do("INCR", rkv.id+":"+key)) if err != nil { return "0", err } return strconv.FormatInt(result, 10), nil } // Remove this key/value func (rkv *KeyValue) Remove() error { conn := rkv.pool.Get(rkv.dbindex) // Find all keys that starts with rkv.id+":" results, err := redis.Values(conn.Do("KEYS", rkv.id+":*")) if err != nil { return err } // For each key id for i := 0; i < len(results); i++ { // Delete this key if _, err = conn.Do("DEL", getString(results, i)); err != nil { return err } } return nil } // Clear the contents func (rkv *KeyValue) Clear() error { return rkv.Remove() } // --- Generic redis functions --- // Check if a key exists. The key can be a wildcard (ie. "user*"). func hasKey(pool *ConnectionPool, wildcard string, dbindex int) (bool, error) { conn := pool.Get(dbindex) result, err := redis.Values(conn.Do("KEYS", wildcard)) if err != nil { return false, err } return len(result) > 0, nil } simpleredis-2.0/simpleredis_test.go000066400000000000000000000077471253116707100176230ustar00rootroot00000000000000package simpleredis import ( "github.com/xyproto/pinterface" "testing" ) var pool *ConnectionPool func TestLocalConnection(t *testing.T) { if err := TestConnection(); err != nil { t.Errorf(err.Error()) } } func TestRemoteConnection(t *testing.T) { if err := TestConnectionHost("foobared@ :6379"); err != nil { t.Errorf(err.Error()) } } func TestConnectionPool(t *testing.T) { pool = NewConnectionPool() } func TestConnectionPoolHost(t *testing.T) { pool = NewConnectionPoolHost("localhost:6379") } // Tests with password "foobared" if the previous connection test // did not result in a connection that responds to PING. func TestConnectionPoolHostPassword(t *testing.T) { if pool.Ping() != nil { // Try connecting with the default password pool = NewConnectionPoolHost("foobared@localhost:6379") } } func TestList(t *testing.T) { const ( listname = "abc123_test_test_test_123abc" testdata = "123abc" ) list := NewList(pool, listname) // Check that the list qualifies for the IList interface var _ pinterface.IList = list list.SelectDatabase(1) if err := list.Add(testdata); err != nil { t.Errorf("Error, could not add item to list! %s", err.Error()) } items, err := list.GetAll() if len(items) != 1 { t.Errorf("Error, wrong list length! %v", len(items)) } if (len(items) > 0) && (items[0] != testdata) { t.Errorf("Error, wrong list contents! %v", items) } err = list.Remove() if err != nil { t.Errorf("Error, could not remove list! %s", err.Error()) } } func TestRemove(t *testing.T) { const ( kvname = "abc123_test_test_test_123abc" testkey = "sdsdf234234" testvalue = "asdfasdf1234" ) kv := NewKeyValue(pool, kvname) // TODO: Also do this check for ISet and IHashMap // Check that the key/value qualifies for the IKeyValue interface var _ pinterface.IKeyValue = kv kv.SelectDatabase(1) if err := kv.Set(testkey, testvalue); err != nil { t.Errorf("Error, could not set key and value! %s", err.Error()) } if val, err := kv.Get(testkey); err != nil { t.Errorf("Error, could not get key! %s", err.Error()) } else if val != testvalue { t.Errorf("Error, wrong value! %s != %s", val, testvalue) } kv.Remove() if _, err := kv.Get(testkey); err == nil { t.Errorf("Error, could get key! %s", err.Error()) } } func TestInc(t *testing.T) { const ( kvname = "kv_234_test_test_test" testkey = "key_234_test_test_test" testvalue0 = "9" testvalue1 = "10" testvalue2 = "1" ) kv := NewKeyValue(pool, kvname) kv.SelectDatabase(1) if err := kv.Set(testkey, testvalue0); err != nil { t.Errorf("Error, could not set key and value! %s", err.Error()) } if val, err := kv.Get(testkey); err != nil { t.Errorf("Error, could not get key! %s", err.Error()) } else if val != testvalue0 { t.Errorf("Error, wrong value! %s != %s", val, testvalue0) } incval, err := kv.Inc(testkey) if err != nil { t.Errorf("Error, could not INCR key! %s", err.Error()) } if val, err := kv.Get(testkey); err != nil { t.Errorf("Error, could not get key! %s", err.Error()) } else if val != testvalue1 { t.Errorf("Error, wrong value! %s != %s", val, testvalue1) } else if incval != testvalue1 { t.Errorf("Error, wrong inc value! %s != %s", incval, testvalue1) } kv.Remove() if _, err := kv.Get(testkey); err == nil { t.Errorf("Error, could get key! %s", err.Error()) } // Creates "0" and increases the value with 1 kv.Inc(testkey) if val, err := kv.Get(testkey); err != nil { t.Errorf("Error, could not get key! %s", err.Error()) } else if val != testvalue2 { t.Errorf("Error, wrong value! %s != %s", val, testvalue2) } kv.Remove() if _, err := kv.Get(testkey); err == nil { t.Errorf("Error, could get key! %s", err.Error()) } } func TestTwoFields(t *testing.T) { test, test23, ok := twoFields("test1@test2@test3", "@") if ok && ((test != "test1") || (test23 != "test2@test3")) { t.Error("Error in twoFields functions") } } func TestICreator(t *testing.T) { // Check if the struct comforms to ICreator var _ pinterface.ICreator = NewCreator(pool, 1) }