github.com/m3db/m3@v1.5.0/src/m3ninx/index/segment/builder/builder_test.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 builder
    22  
    23  import (
    24  	"fmt"
    25  	"sort"
    26  	"testing"
    27  	"unsafe"
    28  
    29  	"github.com/m3db/m3/src/m3ninx/doc"
    30  	"github.com/m3db/m3/src/m3ninx/index"
    31  	"github.com/m3db/m3/src/m3ninx/index/segment"
    32  
    33  	"github.com/stretchr/testify/require"
    34  )
    35  
    36  var (
    37  	testOptions = NewOptions()
    38  
    39  	testDocuments = []doc.Metadata{
    40  		{
    41  			Fields: []doc.Field{
    42  				{
    43  					Name:  []byte("fruit"),
    44  					Value: []byte("banana"),
    45  				},
    46  				{
    47  					Name:  []byte("color"),
    48  					Value: []byte("yellow"),
    49  				},
    50  			},
    51  		},
    52  		{
    53  			Fields: []doc.Field{
    54  				{
    55  					Name:  []byte("fruit"),
    56  					Value: []byte("apple"),
    57  				},
    58  				{
    59  					Name:  []byte("color"),
    60  					Value: []byte("red"),
    61  				},
    62  			},
    63  		},
    64  		{
    65  			ID: []byte("42"),
    66  			Fields: []doc.Field{
    67  				{
    68  					Name:  []byte("fruit"),
    69  					Value: []byte("pineapple"),
    70  				},
    71  				{
    72  					Name:  []byte("color"),
    73  					Value: []byte("yellow"),
    74  				},
    75  			},
    76  		},
    77  	}
    78  )
    79  
    80  func TestBuilderFields(t *testing.T) {
    81  	builder, err := NewBuilderFromDocuments(testOptions)
    82  	require.NoError(t, err)
    83  	defer func() {
    84  		require.NoError(t, builder.Close())
    85  	}()
    86  
    87  	for i := 0; i < 10; i++ {
    88  		builder.Reset()
    89  
    90  		knownsFields := map[string]struct{}{}
    91  		for _, d := range testDocuments {
    92  			for _, f := range d.Fields {
    93  				knownsFields[string(f.Name)] = struct{}{}
    94  			}
    95  			_, err = builder.Insert(d)
    96  			require.NoError(t, err)
    97  		}
    98  
    99  		fieldsIter, err := builder.FieldsPostingsList()
   100  		require.NoError(t, err)
   101  
   102  		fields := toSlice(t, fieldsIter)
   103  		for _, f := range fields {
   104  			delete(knownsFields, string(f))
   105  		}
   106  		require.Empty(t, knownsFields)
   107  	}
   108  }
   109  
   110  func TestBuilderTerms(t *testing.T) {
   111  	builder, err := NewBuilderFromDocuments(testOptions)
   112  	require.NoError(t, err)
   113  	defer func() {
   114  		require.NoError(t, builder.Close())
   115  	}()
   116  
   117  	for i := 0; i < 10; i++ {
   118  		builder.Reset()
   119  
   120  		knownsFields := map[string]map[string]struct{}{}
   121  		for _, d := range testDocuments {
   122  			for _, f := range d.Fields {
   123  				knownVals, ok := knownsFields[string(f.Name)]
   124  				if !ok {
   125  					knownVals = make(map[string]struct{})
   126  					knownsFields[string(f.Name)] = knownVals
   127  				}
   128  				knownVals[string(f.Value)] = struct{}{}
   129  			}
   130  			_, err = builder.Insert(d)
   131  			require.NoError(t, err)
   132  		}
   133  
   134  		for field, expectedTerms := range knownsFields {
   135  			termsIter, err := builder.Terms([]byte(field))
   136  			require.NoError(t, err)
   137  			terms := toTermPostings(t, termsIter)
   138  			for term := range terms {
   139  				delete(expectedTerms, term)
   140  			}
   141  			require.Empty(t, expectedTerms)
   142  		}
   143  	}
   144  }
   145  
   146  // Test that calling Insert(...) API returns correct concrete errors
   147  // instead of partial batch error type.
   148  func TestBuilderInsertDuplicateReturnsErrDuplicateID(t *testing.T) {
   149  	builder, err := NewBuilderFromDocuments(testOptions)
   150  	require.NoError(t, err)
   151  	defer func() {
   152  		require.NoError(t, builder.Close())
   153  	}()
   154  
   155  	_, err = builder.Insert(testDocuments[2])
   156  	require.NoError(t, err)
   157  
   158  	_, err = builder.Insert(testDocuments[2])
   159  	require.Error(t, err)
   160  	require.Equal(t, index.ErrDuplicateID, err)
   161  }
   162  
   163  func toSlice(t *testing.T, iter segment.FieldsPostingsListIterator) [][]byte {
   164  	elems := [][]byte{}
   165  	for iter.Next() {
   166  		b, _ := iter.Current()
   167  		elems = append(elems, b)
   168  	}
   169  	require.NoError(t, iter.Err())
   170  	require.NoError(t, iter.Close())
   171  	return elems
   172  }
   173  
   174  type termPostings map[string][]int
   175  
   176  func toTermPostings(t *testing.T, iter segment.TermsIterator) termPostings {
   177  	elems := make(termPostings)
   178  	for iter.Next() {
   179  		term, postings := iter.Current()
   180  		_, exists := elems[string(term)]
   181  		require.False(t, exists)
   182  
   183  		values := []int{}
   184  		it := postings.Iterator()
   185  		for it.Next() {
   186  			values = append(values, int(it.Current()))
   187  		}
   188  		sort.Sort(sort.IntSlice(values))
   189  
   190  		require.NoError(t, it.Err())
   191  		require.NoError(t, it.Close())
   192  
   193  		elems[string(term)] = values
   194  	}
   195  	require.NoError(t, iter.Err())
   196  	require.NoError(t, iter.Close())
   197  	return elems
   198  }
   199  
   200  // nolint: unused
   201  func printBuilder(t *testing.T, b segment.Builder) {
   202  	fmt.Printf("print builder %x\n", unsafe.Pointer(b.(*builder)))
   203  	fieldsIter, err := b.FieldsPostingsList()
   204  	require.NoError(t, err)
   205  	for fieldsIter.Next() {
   206  		curr, _ := fieldsIter.Current()
   207  		fmt.Printf("builder field: %v\n", string(curr))
   208  		termsIter, err := b.Terms(curr)
   209  		require.NoError(t, err)
   210  		for termsIter.Next() {
   211  			term, postings := termsIter.Current()
   212  			postingsIter := postings.Iterator()
   213  			for postingsIter.Next() {
   214  				posting := postingsIter.Current()
   215  				fmt.Printf("builder term: %s, doc: %d\n", string(term), posting)
   216  			}
   217  		}
   218  	}
   219  	fmt.Println()
   220  }