github.com/m3db/m3@v1.5.0/src/m3ninx/doc/document.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package doc
    22  
    23  import (
    24  	"bytes"
    25  	"errors"
    26  	"fmt"
    27  	"sort"
    28  	"unicode/utf8"
    29  )
    30  
    31  var (
    32  	errReservedFieldName = fmt.Errorf("'%s' is a reserved field name", IDReservedFieldName)
    33  	// ErrEmptyDocument is an error for an empty document.
    34  	ErrEmptyDocument = errors.New("document cannot be empty")
    35  )
    36  
    37  // IDReservedFieldName is the field name reserved for IDs.
    38  var IDReservedFieldName = []byte("_m3ninx_id")
    39  
    40  // Field represents a field in a document. It is composed of a name and a value.
    41  type Field struct {
    42  	Name  []byte
    43  	Value []byte
    44  }
    45  
    46  // Fields is a list of fields.
    47  type Fields []Field
    48  
    49  func (f Fields) Len() int {
    50  	return len(f)
    51  }
    52  
    53  func (f Fields) Less(i, j int) bool {
    54  	l, r := f[i], f[j]
    55  
    56  	c := bytes.Compare(l.Name, r.Name)
    57  	switch {
    58  	case c < 0:
    59  		return true
    60  	case c > 0:
    61  		return false
    62  	}
    63  
    64  	c = bytes.Compare(l.Value, r.Value)
    65  	switch {
    66  	case c < 0:
    67  		return true
    68  	case c > 0:
    69  		return false
    70  	}
    71  
    72  	return true
    73  }
    74  
    75  func (f Fields) Swap(i, j int) {
    76  	f[i], f[j] = f[j], f[i]
    77  }
    78  
    79  func (f Fields) shallowCopy() Fields {
    80  	cp := make([]Field, 0, len(f))
    81  	for _, fld := range f {
    82  		cp = append(cp, Field{
    83  			Name:  fld.Name,
    84  			Value: fld.Value,
    85  		})
    86  	}
    87  	return cp
    88  }
    89  
    90  // Metadata represents a document to be indexed.
    91  type Metadata struct {
    92  	ID            []byte
    93  	Fields        []Field
    94  	OnIndexSeries OnIndexSeries
    95  }
    96  
    97  // Get returns the value of the specified field name in the document if it exists.
    98  func (m Metadata) Get(fieldName []byte) ([]byte, bool) {
    99  	for _, f := range m.Fields { // nolint:gocritic
   100  		if bytes.Equal(fieldName, f.Name) {
   101  			return f.Value, true
   102  		}
   103  	}
   104  	return nil, false
   105  }
   106  
   107  // Compare returns an integer comparing two documents. The result will be 0 if the documents
   108  // are equal, -1 if d is ordered before other, and 1 if d is ordered aftered other.
   109  func (m Metadata) Compare(other Metadata) int {
   110  	if c := bytes.Compare(m.ID, other.ID); c != 0 {
   111  		return c
   112  	}
   113  
   114  	l, r := Fields(m.Fields), Fields(other.Fields)
   115  
   116  	// Make a shallow copy of the Fields so we don't mutate the document.
   117  	if !sort.IsSorted(l) {
   118  		l = l.shallowCopy()
   119  		sort.Sort(l)
   120  	}
   121  	if !sort.IsSorted(r) {
   122  		r = r.shallowCopy()
   123  		sort.Sort(r)
   124  	}
   125  
   126  	min := len(l)
   127  	if len(r) < min {
   128  		min = len(r)
   129  	}
   130  
   131  	for i := 0; i < min; i++ {
   132  		if c := bytes.Compare(l[i].Name, r[i].Name); c != 0 {
   133  			return c
   134  		}
   135  		if c := bytes.Compare(l[i].Value, r[i].Value); c != 0 {
   136  			return c
   137  		}
   138  	}
   139  
   140  	if len(l) < len(r) {
   141  		return -1
   142  	} else if len(l) > len(r) {
   143  		return 1
   144  	}
   145  
   146  	return 0
   147  }
   148  
   149  // Equal returns a bool indicating whether d is equal to other.
   150  func (m Metadata) Equal(other Metadata) bool {
   151  	return m.Compare(other) == 0
   152  }
   153  
   154  // Validate returns a bool indicating whether the document is valid.
   155  func (m Metadata) Validate() error {
   156  	if len(m.Fields) == 0 && !m.HasID() {
   157  		return ErrEmptyDocument
   158  	}
   159  
   160  	if !utf8.Valid(m.ID) {
   161  		return fmt.Errorf("document has invalid ID: id=%v, id_hex=%x", m.ID, m.ID)
   162  	}
   163  
   164  	for _, f := range m.Fields { // nolint:gocritic
   165  		// TODO: Should we enforce uniqueness of field names?
   166  		if !utf8.Valid(f.Name) {
   167  			return fmt.Errorf("document has invalid field name: name=%v, name_hex=%x",
   168  				f.Name, f.Name)
   169  		}
   170  
   171  		if bytes.Equal(f.Name, IDReservedFieldName) {
   172  			return errReservedFieldName
   173  		}
   174  
   175  		if !utf8.Valid(f.Value) {
   176  			return fmt.Errorf("document has invalid field value: value=%v, value_hex=%x",
   177  				f.Value, f.Value)
   178  		}
   179  	}
   180  
   181  	return nil
   182  }
   183  
   184  // HasID returns a bool indicating whether the document has an ID or not.
   185  func (m Metadata) HasID() bool {
   186  	return len(m.ID) > 0
   187  }
   188  
   189  func (m Metadata) String() string {
   190  	var buf bytes.Buffer
   191  	for i, f := range m.Fields { // nolint:gocritic
   192  		buf.WriteString(fmt.Sprintf("%s: %s", f.Name, f.Value))
   193  		if i != len(m.Fields)-1 {
   194  			buf.WriteString(", ")
   195  		}
   196  	}
   197  	return fmt.Sprintf("{id: %s, fields: {%s}}", m.ID, buf.String())
   198  }
   199  
   200  // Documents is a list of documents.
   201  type Documents []Metadata
   202  
   203  func (ds Documents) Len() int {
   204  	return len(ds)
   205  }
   206  
   207  func (ds Documents) Less(i, j int) bool {
   208  	l, r := ds[i], ds[j]
   209  
   210  	return l.Compare(r) < 1
   211  }
   212  
   213  func (ds Documents) Swap(i, j int) {
   214  	ds[i], ds[j] = ds[j], ds[i]
   215  }
   216  
   217  // Encoded is a serialized document metadata.
   218  type Encoded struct {
   219  	Bytes []byte
   220  }
   221  
   222  // Document contains either metadata or an encoded metadata
   223  // but never both.
   224  type Document struct {
   225  	encoded  Encoded
   226  	metadata Metadata
   227  
   228  	hasEncoded  bool
   229  	hasMetadata bool
   230  }
   231  
   232  // NewDocumentFromMetadata creates a Document from a Metadata.
   233  func NewDocumentFromMetadata(m Metadata) Document {
   234  	return Document{metadata: m, hasMetadata: true}
   235  }
   236  
   237  // NewDocumentFromEncoded creates a Document from an Encoded.
   238  func NewDocumentFromEncoded(e Encoded) Document {
   239  	return Document{encoded: e, hasEncoded: true}
   240  }
   241  
   242  // Metadata returns the metadata it contains, if it has one. Otherwise returns an empty metadata
   243  // and false.
   244  func (d *Document) Metadata() (Metadata, bool) {
   245  	if d.hasMetadata {
   246  		return d.metadata, true
   247  	}
   248  
   249  	return Metadata{}, false
   250  }
   251  
   252  // Encoded returns the encoded metadata it contains, if it has one. Otherwise returns an
   253  // empty encoded metadata and false.
   254  func (d *Document) Encoded() (Encoded, bool) {
   255  	if d.hasEncoded {
   256  		return d.encoded, true
   257  	}
   258  
   259  	return Encoded{}, false
   260  }