// Ensure the multi-cursor can correctly iterate across multiple non-overlapping subcursors. func TestMultiCursor_Multiple_NonOverlapping_Reverse(t *testing.T) { mc := tsdb.MultiCursor( NewCursor([]CursorItem{ {Key: 0, Value: 0}, {Key: 3, Value: 30}, {Key: 4, Value: 40}, }, false), NewCursor([]CursorItem{ {Key: 1, Value: 10}, {Key: 2, Value: 20}, }, false), ) if k, v := mc.SeekTo(4); k != 4 || v.(int) != 40 { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); k != 3 || v.(int) != 30 { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); k != 2 || v.(int) != 20 { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); k != 1 || v.(int) != 10 { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); k != 0 || v.(int) != 00 { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); k != tsdb.EOF { t.Fatalf("expected eof, got: %x / %x", k, v) } }
// Ensure the multi-cursor can correctly iterate across multiple overlapping subcursors. func TestMultiCursor_Multiple_Overlapping(t *testing.T) { mc := tsdb.MultiCursor( NewCursor([]CursorItem{ {Key: []byte{0x00}, Value: []byte{0x00}}, {Key: []byte{0x03}, Value: []byte{0x03}}, {Key: []byte{0x04}, Value: []byte{0x04}}, }), NewCursor([]CursorItem{ {Key: []byte{0x00}, Value: []byte{0xF0}}, {Key: []byte{0x02}, Value: []byte{0xF2}}, {Key: []byte{0x04}, Value: []byte{0xF4}}, }), ) if k, v := mc.Seek([]byte{0x00}); !bytes.Equal(k, []byte{0x00}) || !bytes.Equal(v, []byte{0x00}) { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); !bytes.Equal(k, []byte{0x02}) || !bytes.Equal(v, []byte{0xF2}) { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); !bytes.Equal(k, []byte{0x03}) || !bytes.Equal(v, []byte{0x03}) { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); !bytes.Equal(k, []byte{0x04}) || !bytes.Equal(v, []byte{0x04}) { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); k != nil { t.Fatalf("expected eof, got: %x / %x", k, v) } }
// Ensure the multi-cursor can correctly iterate across multiple overlapping subcursors. func TestMultiCursor_Multiple_Overlapping(t *testing.T) { mc := tsdb.MultiCursor( NewCursor([]CursorItem{ {Key: 0, Value: 0}, {Key: 3, Value: 3}, {Key: 4, Value: 4}, }, true), NewCursor([]CursorItem{ {Key: 0, Value: 0xF0}, {Key: 2, Value: 0xF2}, {Key: 4, Value: 0xF4}, }, true), ) if k, v := mc.SeekTo(0); k != 0 || v.(int) != 0 { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); k != 2 || v.(int) != 0xF2 { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); k != 3 || v.(int) != 3 { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); k != 4 || v.(int) != 4 { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); k != tsdb.EOF { t.Fatalf("expected eof, got: %x / %x", k, v) } }
// Cursor returns an iterator for a key. func (tx *Tx) Cursor(key string) tsdb.Cursor { walCursor := tx.wal.Cursor(key) // Retrieve points bucket. Ignore if there is no bucket. b := tx.Bucket([]byte("points")).Bucket([]byte(key)) if b == nil { return walCursor } c := &Cursor{ cursor: b.Cursor(), } return tsdb.MultiCursor(walCursor, c) }
// Ensure the multi-cursor can correctly iterate across a single subcursor in reverse order. func TestMultiCursor_Single_Reverse(t *testing.T) { mc := tsdb.MultiCursor(NewCursor([]CursorItem{ {Key: 0, Value: 0}, {Key: 1, Value: 10}, {Key: 2, Value: 20}, }, false)) if k, v := mc.SeekTo(2); k != 2 || v.(int) != 20 { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); k != 1 || v.(int) != 10 { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); k != 0 || v.(int) != 0 { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); k != tsdb.EOF { t.Fatalf("expected eof, got: %x / %x", k, v) } }
// Ensure the multi-cursor can correctly iterate across a single subcursor. func TestMultiCursor_Single(t *testing.T) { mc := tsdb.MultiCursor( NewCursor([]CursorItem{ {Key: []byte{0x00}, Value: []byte{0x00}}, {Key: []byte{0x01}, Value: []byte{0x10}}, {Key: []byte{0x02}, Value: []byte{0x20}}, }), ) if k, v := mc.Seek([]byte{0x00}); !bytes.Equal(k, []byte{0x00}) || !bytes.Equal(v, []byte{0x00}) { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); !bytes.Equal(k, []byte{0x01}) || !bytes.Equal(v, []byte{0x10}) { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); !bytes.Equal(k, []byte{0x02}) || !bytes.Equal(v, []byte{0x20}) { t.Fatalf("unexpected key/value: %x / %x", k, v) } else if k, v = mc.Next(); k != nil { t.Fatalf("expected eof, got: %x / %x", k, v) } }
// Cursor returns an iterator for a key. func (tx *Tx) Cursor(key string, direction tsdb.Direction) tsdb.Cursor { walCursor := tx.wal.Cursor(key, direction) // Retrieve points bucket. Ignore if there is no bucket. b := tx.Bucket([]byte("points")).Bucket([]byte(key)) if b == nil { return walCursor } c := &Cursor{ cursor: b.Cursor(), direction: direction, } if direction.Reverse() { c.last() } return tsdb.MultiCursor(direction, walCursor, c) }
// Ensure the multi-cursor can handle randomly generated data. func TestMultiCursor_Quick(t *testing.T) { quick.Check(func(seek uint64, cursors []Cursor) bool { var got, exp [][]byte seek %= 100 // Merge all cursor data to determine expected output. // First seen key overrides all other items with the same key. m := make(map[string][]byte) for _, c := range cursors { for _, item := range c.items { if bytes.Compare(item.Key, u64tob(seek)) == -1 { continue } if _, ok := m[string(item.Key)]; ok { continue } m[string(item.Key)] = item.Value } } // Convert map back to single item list. for k, v := range m { exp = append(exp, append([]byte(k), v...)) } sort.Sort(byteSlices(exp)) // Create multi-cursor and iterate over all items. mc := tsdb.MultiCursor(tsdbCursorSlice(cursors)...) for k, v := mc.Seek(u64tob(seek)); k != nil; k, v = mc.Next() { got = append(got, append(k, v...)) } // Verify results. if !reflect.DeepEqual(got, exp) { t.Fatalf("mismatch: seek=%d\n\ngot=%+v\n\nexp=%+v", seek, got, exp) } return true }, nil) }
// Ensure the multi-cursor can handle randomly generated data. func TestMultiCursor_Quick(t *testing.T) { quick.Check(func(useek uint64, cursors []Cursor) bool { var got, exp []CursorItem seek := int64(useek) % 100 // Merge all cursor data to determine expected output. // First seen key overrides all other items with the same key. m := make(map[int64]CursorItem) for _, c := range cursors { for _, item := range c.items { if item.Key < seek { continue } if _, ok := m[item.Key]; ok { continue } m[item.Key] = item } } // Convert map back to single item list. for _, item := range m { exp = append(exp, item) } sort.Sort(CursorItems(exp)) // Create multi-cursor and iterate over all items. mc := tsdb.MultiCursor(tsdbCursorSlice(cursors)...) for k, v := mc.SeekTo(seek); k != tsdb.EOF; k, v = mc.Next() { got = append(got, CursorItem{k, v.(int)}) } // Verify results. if !reflect.DeepEqual(got, exp) { t.Fatalf("mismatch: seek=%d\n\ngot=%+v\n\nexp=%+v", seek, got, exp) } return true }, nil) }
// Cursor returns an iterator for a key. func (tx *Tx) Cursor(series string, fields []string, dec *tsdb.FieldCodec, ascending bool) tsdb.Cursor { walCursor := tx.wal.Cursor(series, fields, dec, ascending) // Retrieve points bucket. Ignore if there is no bucket. b := tx.Bucket([]byte("points")).Bucket([]byte(series)) if b == nil { return walCursor } c := &Cursor{ cursor: b.Cursor(), fields: fields, dec: dec, ascending: ascending, } if !ascending { c.last() } return tsdb.MultiCursor(walCursor, c) }