diff --git a/reader.go b/reader.go index 9e9129665..fa0dca9b5 100644 --- a/reader.go +++ b/reader.go @@ -61,15 +61,12 @@ type IndexReader interface { Series(ref uint32) (Series, error) } -// StringTuple is a tuple of strings. -type StringTuple []string - // StringTuples provides access to a sorted list of string tuples. type StringTuples interface { // Total number of tuples in the list. Len() int // At returns the tuple at position i. - At(i int) (StringTuple, error) + At(i int) ([]string, error) } type indexReader struct { @@ -188,4 +185,86 @@ func (r *indexReader) LabelValues(names ...string) (StringTuples, error) { if err != nil { return nil, fmt.Errorf("section: %s", err) } + if flag != flagStd { + return nil, errInvalidFlag + } + if len(b) < 1 { + return nil, errInvalidSize + } + + st := &serializedStringTuples{ + l: int(b[0]), + b: b[1:], + lookup: r.lookupSymbol, + } + return st, nil +} + +type stringTuples struct { + l int // tuple length + s []string // flattened tuple entries +} + +func newStringTuples(s []string, l int) (*stringTuples, error) { + if len(s)%l != 0 { + return nil, errInvalidSize + } + return &stringTuples{s: s, l: l}, nil +} + +func (t *stringTuples) Len() int { return len(t.s) / t.l } +func (t *stringTuples) At(i int) ([]string, error) { return t.s[i : i+t.l], nil } + +func (t *stringTuples) Swap(i, j int) { + c := make([]string, t.l) + copy(c, t.s[i:i+t.l]) + + for k := 0; k < t.l; k++ { + t.s[i+k] = t.s[j+k] + t.s[j+k] = c[k] + } +} + +func (t *stringTuples) Less(i, j int) bool { + for k := 0; k < t.l; k++ { + d := strings.Compare(t.s[i+k], t.s[j+k]) + + if d < 0 { + return true + } + if d > 0 { + return false + } + } + return false +} + +type serializedStringTuples struct { + l int + b []byte + lookup func(uint32) ([]byte, error) +} + +func (t *serializedStringTuples) Len() int { + // TODO(fabxc): Cache this? + return len(t.b) / (4 * t.l) +} + +func (t *serializedStringTuples) At(i int) ([]string, error) { + if len(t.b) < (i+t.l)*4 { + return nil, errInvalidSize + } + res := make([]string, t.l) + + for k := 0; k < t.l; k++ { + offset := binary.BigEndian.Uint32(t.b[i*4:]) + + b, err := t.lookup(offset) + if err != nil { + return nil, fmt.Errorf("lookup: %s", err) + } + res = append(res, string(b)) + } + + return res, nil } diff --git a/writer.go b/writer.go index 13588e38d..470746c4c 100644 --- a/writer.go +++ b/writer.go @@ -2,11 +2,11 @@ package tsdb import ( "encoding/binary" - "fmt" "hash/crc32" "io" "os" "sort" + "strings" ) const ( @@ -319,26 +319,32 @@ func (w *indexWriter) writeSeries() error { } func (w *indexWriter) WriteLabelIndex(names []string, values []string) error { - if len(names) != 1 { - return fmt.Errorf("not supported") + valt, err := newStringTuples(values, len(names)) + if err != nil { + return err } - sort.Strings(values) + sort.Sort(valt) w.labelIndexes = append(w.labelIndexes, hashEntry{ - name: names[0], + name: strings.Join(names, string(sep)), offset: uint32(w.n), }) - l := uint32(len(values) * 4) + l := 1 + uint32(len(values)*4) return w.section(l, flagStd, func(wr io.Writer) error { - for _, v := range values { - o := w.symbols[v] + // First byte indicates tuple size for index. + if err := w.write(wr, []byte{byte(len(names))}); err != nil { + return err + } + buf := make([]byte, 4) - if err := binary.Write(wr, binary.BigEndian, o); err != nil { + for _, v := range valt.s { + binary.BigEndian.PutUint32(buf, w.symbols[v]) + + if err := w.write(wr, buf); err != nil { return err } - w.n += 4 } return nil })