...

Source file src/github.com/gomodule/redigo/redis/scan_test.go

Documentation: github.com/gomodule/redigo/redis

     1  // Copyright 2012 Gary Burd
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License"): you may
     4  // not use this file except in compliance with the License. You may obtain
     5  // a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    11  // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    12  // License for the specific language governing permissions and limitations
    13  // under the License.
    14  
    15  package redis_test
    16  
    17  import (
    18  	"fmt"
    19  	"math"
    20  	"reflect"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/gomodule/redigo/redis"
    25  )
    26  
    27  type durationScan struct {
    28  	time.Duration `redis:"sd"`
    29  }
    30  
    31  func (t *durationScan) RedisScan(src interface{}) (err error) {
    32  	if t == nil {
    33  		return fmt.Errorf("nil pointer")
    34  	}
    35  	switch src := src.(type) {
    36  	case string:
    37  		t.Duration, err = time.ParseDuration(src)
    38  	case []byte:
    39  		t.Duration, err = time.ParseDuration(string(src))
    40  	case int64:
    41  		t.Duration = time.Duration(src)
    42  	default:
    43  		err = fmt.Errorf("cannot convert from %T to %T", src, t)
    44  	}
    45  	return err
    46  }
    47  
    48  var scanConversionTests = []struct {
    49  	src  interface{}
    50  	dest interface{}
    51  }{
    52  	{[]byte("-inf"), math.Inf(-1)},
    53  	{[]byte("+inf"), math.Inf(1)},
    54  	{[]byte("0"), float64(0)},
    55  	{[]byte("3.14159"), float64(3.14159)},
    56  	{[]byte("3.14"), float32(3.14)},
    57  	{[]byte("-100"), int(-100)},
    58  	{[]byte("101"), int(101)},
    59  	{int64(102), int(102)},
    60  	{[]byte("103"), uint(103)},
    61  	{int64(104), uint(104)},
    62  	{[]byte("105"), int8(105)},
    63  	{int64(106), int8(106)},
    64  	{[]byte("107"), uint8(107)},
    65  	{int64(108), uint8(108)},
    66  	{[]byte("0"), false},
    67  	{int64(0), false},
    68  	{[]byte("f"), false},
    69  	{[]byte("1"), true},
    70  	{int64(1), true},
    71  	{[]byte("t"), true},
    72  	{"hello", "hello"},
    73  	{[]byte("hello"), "hello"},
    74  	{[]byte("world"), []byte("world")},
    75  	{[]interface{}{[]byte("foo")}, []interface{}{[]byte("foo")}},
    76  	{[]interface{}{[]byte("foo")}, []string{"foo"}},
    77  	{[]interface{}{[]byte("hello"), []byte("world")}, []string{"hello", "world"}},
    78  	{[]interface{}{[]byte("bar")}, [][]byte{[]byte("bar")}},
    79  	{[]interface{}{[]byte("1")}, []int{1}},
    80  	{[]interface{}{[]byte("1"), []byte("2")}, []int{1, 2}},
    81  	{[]interface{}{[]byte("1"), []byte("2")}, []float64{1, 2}},
    82  	{[]interface{}{[]byte("1")}, []byte{1}},
    83  	{[]interface{}{[]byte("1")}, []bool{true}},
    84  	{"1m", durationScan{Duration: time.Minute}},
    85  	{[]byte("1m"), durationScan{Duration: time.Minute}},
    86  	{time.Minute.Nanoseconds(), durationScan{Duration: time.Minute}},
    87  	{[]interface{}{[]byte("1m")}, []durationScan{{Duration: time.Minute}}},
    88  	{[]interface{}{[]byte("1m")}, []*durationScan{{Duration: time.Minute}}},
    89  }
    90  
    91  func TestScanConversion(t *testing.T) {
    92  	for _, tt := range scanConversionTests {
    93  		values := []interface{}{tt.src}
    94  		dest := reflect.New(reflect.TypeOf(tt.dest))
    95  		values, err := redis.Scan(values, dest.Interface())
    96  		if err != nil {
    97  			t.Errorf("Scan(%v) returned error %v", tt, err)
    98  			continue
    99  		}
   100  		if !reflect.DeepEqual(tt.dest, dest.Elem().Interface()) {
   101  			t.Errorf("Scan(%v) returned %v, want %v", tt, dest.Elem().Interface(), tt.dest)
   102  		}
   103  	}
   104  }
   105  
   106  var scanConversionErrorTests = []struct {
   107  	src  interface{}
   108  	dest interface{}
   109  }{
   110  	{[]byte("1234"), byte(0)},
   111  	{int64(1234), byte(0)},
   112  	{[]byte("-1"), byte(0)},
   113  	{int64(-1), byte(0)},
   114  	{[]byte("junk"), false},
   115  	{redis.Error("blah"), false},
   116  	{redis.Error("blah"), durationScan{Duration: time.Minute}},
   117  	{"invalid", durationScan{Duration: time.Minute}},
   118  }
   119  
   120  func TestScanConversionError(t *testing.T) {
   121  	for _, tt := range scanConversionErrorTests {
   122  		values := []interface{}{tt.src}
   123  		dest := reflect.New(reflect.TypeOf(tt.dest))
   124  		values, err := redis.Scan(values, dest.Interface())
   125  		if err == nil {
   126  			t.Errorf("Scan(%v) did not return error", tt)
   127  		}
   128  	}
   129  }
   130  
   131  func ExampleScan() {
   132  	c, err := dial()
   133  	if err != nil {
   134  		fmt.Println(err)
   135  		return
   136  	}
   137  	defer c.Close()
   138  
   139  	c.Send("HMSET", "album:1", "title", "Red", "rating", 5)
   140  	c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1)
   141  	c.Send("HMSET", "album:3", "title", "Beat")
   142  	c.Send("LPUSH", "albums", "1")
   143  	c.Send("LPUSH", "albums", "2")
   144  	c.Send("LPUSH", "albums", "3")
   145  	values, err := redis.Values(c.Do("SORT", "albums",
   146  		"BY", "album:*->rating",
   147  		"GET", "album:*->title",
   148  		"GET", "album:*->rating"))
   149  	if err != nil {
   150  		fmt.Println(err)
   151  		return
   152  	}
   153  
   154  	for len(values) > 0 {
   155  		var title string
   156  		rating := -1 // initialize to illegal value to detect nil.
   157  		values, err = redis.Scan(values, &title, &rating)
   158  		if err != nil {
   159  			fmt.Println(err)
   160  			return
   161  		}
   162  		if rating == -1 {
   163  			fmt.Println(title, "not-rated")
   164  		} else {
   165  			fmt.Println(title, rating)
   166  		}
   167  	}
   168  	// Output:
   169  	// Beat not-rated
   170  	// Earthbound 1
   171  	// Red 5
   172  }
   173  
   174  type s0 struct {
   175  	X  int
   176  	Y  int `redis:"y"`
   177  	Bt bool
   178  }
   179  
   180  type s1 struct {
   181  	X  int    `redis:"-"`
   182  	I  int    `redis:"i"`
   183  	U  uint   `redis:"u"`
   184  	S  string `redis:"s"`
   185  	P  []byte `redis:"p"`
   186  	B  bool   `redis:"b"`
   187  	Bt bool
   188  	Bf bool
   189  	s0
   190  	Sd  durationScan  `redis:"sd"`
   191  	Sdp *durationScan `redis:"sdp"`
   192  }
   193  
   194  var scanStructTests = []struct {
   195  	title string
   196  	reply []string
   197  	value interface{}
   198  }{
   199  	{"basic",
   200  		[]string{
   201  			"i", "-1234",
   202  			"u", "5678",
   203  			"s", "hello",
   204  			"p", "world",
   205  			"b", "t",
   206  			"Bt", "1",
   207  			"Bf", "0",
   208  			"X", "123",
   209  			"y", "456",
   210  			"sd", "1m",
   211  			"sdp", "1m",
   212  		},
   213  		&s1{
   214  			I:   -1234,
   215  			U:   5678,
   216  			S:   "hello",
   217  			P:   []byte("world"),
   218  			B:   true,
   219  			Bt:  true,
   220  			Bf:  false,
   221  			s0:  s0{X: 123, Y: 456},
   222  			Sd:  durationScan{Duration: time.Minute},
   223  			Sdp: &durationScan{Duration: time.Minute},
   224  		},
   225  	},
   226  }
   227  
   228  func TestScanStruct(t *testing.T) {
   229  	for _, tt := range scanStructTests {
   230  
   231  		var reply []interface{}
   232  		for _, v := range tt.reply {
   233  			reply = append(reply, []byte(v))
   234  		}
   235  
   236  		value := reflect.New(reflect.ValueOf(tt.value).Type().Elem())
   237  
   238  		if err := redis.ScanStruct(reply, value.Interface()); err != nil {
   239  			t.Fatalf("ScanStruct(%s) returned error %v", tt.title, err)
   240  		}
   241  
   242  		if !reflect.DeepEqual(value.Interface(), tt.value) {
   243  			t.Fatalf("ScanStruct(%s) returned %v, want %v", tt.title, value.Interface(), tt.value)
   244  		}
   245  	}
   246  }
   247  
   248  func TestBadScanStructArgs(t *testing.T) {
   249  	x := []interface{}{"A", "b"}
   250  	test := func(v interface{}) {
   251  		if err := redis.ScanStruct(x, v); err == nil {
   252  			t.Errorf("Expect error for ScanStruct(%T, %T)", x, v)
   253  		}
   254  	}
   255  
   256  	test(nil)
   257  
   258  	var v0 *struct{}
   259  	test(v0)
   260  
   261  	var v1 int
   262  	test(&v1)
   263  
   264  	x = x[:1]
   265  	v2 := struct{ A string }{}
   266  	test(&v2)
   267  }
   268  
   269  var scanSliceTests = []struct {
   270  	src        []interface{}
   271  	fieldNames []string
   272  	ok         bool
   273  	dest       interface{}
   274  }{
   275  	{
   276  		[]interface{}{[]byte("1"), nil, []byte("-1")},
   277  		nil,
   278  		true,
   279  		[]int{1, 0, -1},
   280  	},
   281  	{
   282  		[]interface{}{[]byte("1"), nil, []byte("2")},
   283  		nil,
   284  		true,
   285  		[]uint{1, 0, 2},
   286  	},
   287  	{
   288  		[]interface{}{[]byte("-1")},
   289  		nil,
   290  		false,
   291  		[]uint{1},
   292  	},
   293  	{
   294  		[]interface{}{[]byte("hello"), nil, []byte("world")},
   295  		nil,
   296  		true,
   297  		[][]byte{[]byte("hello"), nil, []byte("world")},
   298  	},
   299  	{
   300  		[]interface{}{[]byte("hello"), nil, []byte("world")},
   301  		nil,
   302  		true,
   303  		[]string{"hello", "", "world"},
   304  	},
   305  	{
   306  		[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
   307  		nil,
   308  		true,
   309  		[]struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}},
   310  	},
   311  	{
   312  		[]interface{}{[]byte("a1"), []byte("b1")},
   313  		nil,
   314  		false,
   315  		[]struct{ A, B, C string }{{"a1", "b1", ""}},
   316  	},
   317  	{
   318  		[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
   319  		nil,
   320  		true,
   321  		[]*struct{ A, B string }{{A: "a1", B: "b1"}, {A: "a2", B: "b2"}},
   322  	},
   323  	{
   324  		[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
   325  		[]string{"A", "B"},
   326  		true,
   327  		[]struct{ A, C, B string }{{"a1", "", "b1"}, {"a2", "", "b2"}},
   328  	},
   329  	{
   330  		[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
   331  		nil,
   332  		false,
   333  		[]struct{}{},
   334  	},
   335  }
   336  
   337  func TestScanSlice(t *testing.T) {
   338  	for _, tt := range scanSliceTests {
   339  
   340  		typ := reflect.ValueOf(tt.dest).Type()
   341  		dest := reflect.New(typ)
   342  
   343  		err := redis.ScanSlice(tt.src, dest.Interface(), tt.fieldNames...)
   344  		if tt.ok != (err == nil) {
   345  			t.Errorf("ScanSlice(%v, []%s, %v) returned error %v", tt.src, typ, tt.fieldNames, err)
   346  			continue
   347  		}
   348  		if tt.ok && !reflect.DeepEqual(dest.Elem().Interface(), tt.dest) {
   349  			t.Errorf("ScanSlice(src, []%s) returned %#v, want %#v", typ, dest.Elem().Interface(), tt.dest)
   350  		}
   351  	}
   352  }
   353  
   354  func ExampleScanSlice() {
   355  	c, err := dial()
   356  	if err != nil {
   357  		fmt.Println(err)
   358  		return
   359  	}
   360  	defer c.Close()
   361  
   362  	c.Send("HMSET", "album:1", "title", "Red", "rating", 5)
   363  	c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1)
   364  	c.Send("HMSET", "album:3", "title", "Beat", "rating", 4)
   365  	c.Send("LPUSH", "albums", "1")
   366  	c.Send("LPUSH", "albums", "2")
   367  	c.Send("LPUSH", "albums", "3")
   368  	values, err := redis.Values(c.Do("SORT", "albums",
   369  		"BY", "album:*->rating",
   370  		"GET", "album:*->title",
   371  		"GET", "album:*->rating"))
   372  	if err != nil {
   373  		fmt.Println(err)
   374  		return
   375  	}
   376  
   377  	var albums []struct {
   378  		Title  string
   379  		Rating int
   380  	}
   381  	if err := redis.ScanSlice(values, &albums); err != nil {
   382  		fmt.Println(err)
   383  		return
   384  	}
   385  	fmt.Printf("%v\n", albums)
   386  	// Output:
   387  	// [{Earthbound 1} {Beat 4} {Red 5}]
   388  }
   389  
   390  var argsTests = []struct {
   391  	title    string
   392  	actual   redis.Args
   393  	expected redis.Args
   394  }{
   395  	{"struct ptr",
   396  		redis.Args{}.AddFlat(&struct {
   397  			I  int               `redis:"i"`
   398  			U  uint              `redis:"u"`
   399  			S  string            `redis:"s"`
   400  			P  []byte            `redis:"p"`
   401  			M  map[string]string `redis:"m"`
   402  			Bt bool
   403  			Bf bool
   404  		}{
   405  			-1234, 5678, "hello", []byte("world"), map[string]string{"hello": "world"}, true, false,
   406  		}),
   407  		redis.Args{"i", int(-1234), "u", uint(5678), "s", "hello", "p", []byte("world"), "m", map[string]string{"hello": "world"}, "Bt", true, "Bf", false},
   408  	},
   409  	{"struct",
   410  		redis.Args{}.AddFlat(struct{ I int }{123}),
   411  		redis.Args{"I", 123},
   412  	},
   413  	{"slice",
   414  		redis.Args{}.Add(1).AddFlat([]string{"a", "b", "c"}).Add(2),
   415  		redis.Args{1, "a", "b", "c", 2},
   416  	},
   417  	{"struct omitempty",
   418  		redis.Args{}.AddFlat(&struct {
   419  			I  int               `redis:"i,omitempty"`
   420  			U  uint              `redis:"u,omitempty"`
   421  			S  string            `redis:"s,omitempty"`
   422  			P  []byte            `redis:"p,omitempty"`
   423  			M  map[string]string `redis:"m,omitempty"`
   424  			Bt bool              `redis:"Bt,omitempty"`
   425  			Bf bool              `redis:"Bf,omitempty"`
   426  		}{
   427  			0, 0, "", []byte{}, map[string]string{}, true, false,
   428  		}),
   429  		redis.Args{"Bt", true},
   430  	},
   431  }
   432  
   433  func TestArgs(t *testing.T) {
   434  	for _, tt := range argsTests {
   435  		if !reflect.DeepEqual(tt.actual, tt.expected) {
   436  			t.Fatalf("%s is %v, want %v", tt.title, tt.actual, tt.expected)
   437  		}
   438  	}
   439  }
   440  
   441  func ExampleArgs() {
   442  	c, err := dial()
   443  	if err != nil {
   444  		fmt.Println(err)
   445  		return
   446  	}
   447  	defer c.Close()
   448  
   449  	var p1, p2 struct {
   450  		Title  string `redis:"title"`
   451  		Author string `redis:"author"`
   452  		Body   string `redis:"body"`
   453  	}
   454  
   455  	p1.Title = "Example"
   456  	p1.Author = "Gary"
   457  	p1.Body = "Hello"
   458  
   459  	if _, err := c.Do("HMSET", redis.Args{}.Add("id1").AddFlat(&p1)...); err != nil {
   460  		fmt.Println(err)
   461  		return
   462  	}
   463  
   464  	m := map[string]string{
   465  		"title":  "Example2",
   466  		"author": "Steve",
   467  		"body":   "Map",
   468  	}
   469  
   470  	if _, err := c.Do("HMSET", redis.Args{}.Add("id2").AddFlat(m)...); err != nil {
   471  		fmt.Println(err)
   472  		return
   473  	}
   474  
   475  	for _, id := range []string{"id1", "id2"} {
   476  
   477  		v, err := redis.Values(c.Do("HGETALL", id))
   478  		if err != nil {
   479  			fmt.Println(err)
   480  			return
   481  		}
   482  
   483  		if err := redis.ScanStruct(v, &p2); err != nil {
   484  			fmt.Println(err)
   485  			return
   486  		}
   487  
   488  		fmt.Printf("%+v\n", p2)
   489  	}
   490  
   491  	// Output:
   492  	// {Title:Example Author:Gary Body:Hello}
   493  	// {Title:Example2 Author:Steve Body:Map}
   494  }
   495  

View as plain text