Skip to content

Commit e87deac

Browse files
committed
Add TimeFieldIndex
1 parent fcf5d84 commit e87deac

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed

index.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"reflect"
99
"strconv"
1010
"strings"
11+
"time"
1112
)
1213

1314
// Indexer is an interface used for defining indexes. Indexes are used
@@ -670,6 +671,73 @@ func fromBoolArgs(args []interface{}) ([]byte, error) {
670671
return []byte{0}, nil
671672
}
672673

674+
// TimeFieldIndex is used to extract a time.Time field from an object using
675+
// reflection and builds an index on that field.
676+
type TimeFieldIndex struct {
677+
Field string
678+
}
679+
680+
func (u *TimeFieldIndex) FromObject(obj interface{}) (bool, []byte, error) {
681+
v := reflect.ValueOf(obj)
682+
v = reflect.Indirect(v) // Dereference the pointer if any
683+
684+
fv := v.FieldByName(u.Field)
685+
if !fv.IsValid() {
686+
return false, nil,
687+
fmt.Errorf("field '%s' for %#v is invalid", u.Field, obj)
688+
}
689+
690+
// Check the type
691+
k := fv.Kind()
692+
693+
if ok := IsTimeType(k); !ok {
694+
return false, nil, fmt.Errorf("field %q is of type %v; want a time."+
695+
"Time", u.Field, k)
696+
}
697+
698+
// Get the value and encode it
699+
val := fv.Interface().(time.Time)
700+
bufUnix := encodeInt(val.Unix(), 8)
701+
bufNano := encodeInt(int64(val.Nanosecond()), 4)
702+
buf := append(bufUnix, bufNano...)
703+
704+
return true, buf, nil
705+
}
706+
707+
func (u *TimeFieldIndex) FromArgs(args ...interface{}) ([]byte, error) {
708+
if len(args) != 1 {
709+
return nil, fmt.Errorf("must provide only a single argument")
710+
}
711+
712+
v := reflect.ValueOf(args[0])
713+
if !v.IsValid() {
714+
return nil, fmt.Errorf("%#v is invalid", args[0])
715+
}
716+
717+
k := v.Kind()
718+
719+
if ok := IsTimeType(k); !ok {
720+
return nil, fmt.Errorf("arg is of type %v; want a time.Time", k)
721+
}
722+
723+
val := v.Interface().(time.Time)
724+
bufUnix := encodeInt(val.Unix(), 8)
725+
bufNano := encodeInt(int64(val.Nanosecond()), 4)
726+
buf := append(bufUnix, bufNano...)
727+
728+
return buf, nil
729+
}
730+
731+
// IsTimeType returns whether the passed type is a type of time.Time.
732+
func IsTimeType(k reflect.Kind) (okay bool) {
733+
switch k {
734+
case reflect.TypeOf(time.Time{}).Kind():
735+
return true
736+
default:
737+
return false
738+
}
739+
}
740+
673741
// CompoundIndex is used to build an index using multiple sub-indexes
674742
// Prefix based iteration is supported as long as the appropriate prefix
675743
// of indexers support it. All sub-indexers are only assumed to expect

index_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type TestObject struct {
3838
Uint32 uint32
3939
Uint64 uint64
4040
Bool bool
41+
Time time.Time
4142
}
4243

4344
func String(s string) *string {
@@ -71,6 +72,7 @@ func testObj() *TestObject {
7172
Uint32: uint32(1<<32 - 1),
7273
Uint64: uint64(1<<64 - 1),
7374
Bool: false,
75+
Time: time.Unix(0, 0),
7476
}
7577
return obj
7678
}
@@ -1224,6 +1226,79 @@ func TestConditionalIndex_FromArgs(t *testing.T) {
12241226
}
12251227
}
12261228

1229+
func TestTimeFieldIndex_FromObject(t *testing.T) {
1230+
obj := testObj()
1231+
indexer := TimeFieldIndex{Field: "Time"}
1232+
obj.Time = time.Unix(0, 0)
1233+
ok, val, err := indexer.FromObject(obj)
1234+
if err != nil {
1235+
t.Fatalf("err: %v", err)
1236+
}
1237+
if !ok {
1238+
t.Fatalf("should be ok")
1239+
}
1240+
1241+
etime := make([]byte, 12)
1242+
binary.BigEndian.PutUint64(etime, 1<<63)
1243+
binary.BigEndian.PutUint32(etime[8:], 1<<31)
1244+
if !bytes.Equal(val, etime) {
1245+
t.Fatalf("bad: %#v %#v", val, etime)
1246+
}
1247+
1248+
// Change the object so it should return with 1 nano.
1249+
obj.Time = time.Unix(0, 1)
1250+
ok, val, err = indexer.FromObject(obj)
1251+
if err != nil {
1252+
t.Fatalf("err: %v", err)
1253+
}
1254+
if !ok {
1255+
t.Fatalf("should be ok")
1256+
}
1257+
1258+
binary.BigEndian.PutUint64(etime, 1<<63)
1259+
binary.BigEndian.PutUint32(etime[8:], 1<<31+1)
1260+
if !bytes.Equal(val, etime) {
1261+
t.Fatalf("bad: %#v %#v", val, etime)
1262+
}
1263+
1264+
// Pass an invalid type.
1265+
ok, val, err = indexer.FromObject(t)
1266+
if err == nil {
1267+
t.Fatalf("expected an error when passing invalid type")
1268+
}
1269+
}
1270+
1271+
func TestTimeFieldIndex_FromArgs(t *testing.T) {
1272+
indexer := TimeFieldIndex{"Foo"}
1273+
_, err := indexer.FromArgs()
1274+
if err == nil {
1275+
t.Fatalf("should get err")
1276+
}
1277+
1278+
_, err = indexer.FromArgs(int(1), int(2))
1279+
if err == nil {
1280+
t.Fatalf("should get err")
1281+
}
1282+
1283+
_, err = indexer.FromArgs("foo")
1284+
if err == nil {
1285+
t.Fatalf("should get err")
1286+
}
1287+
1288+
obj := testObj()
1289+
etime := make([]byte, 12)
1290+
binary.BigEndian.PutUint64(etime, 1<<63)
1291+
binary.BigEndian.PutUint32(etime[8:], 1<<31)
1292+
1293+
val, err := indexer.FromArgs(obj.Time)
1294+
if err != nil {
1295+
t.Fatalf("bad: %v", err)
1296+
}
1297+
if !bytes.Equal(val, etime) {
1298+
t.Fatalf("bad: %#v %#v", val, etime)
1299+
}
1300+
}
1301+
12271302
func TestCompoundIndex_FromObject(t *testing.T) {
12281303
obj := testObj()
12291304
indexer := &CompoundIndex{

0 commit comments

Comments
 (0)