github.com/m3db/m3@v1.5.0/src/m3ninx/search/proptest/query_gen.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 proptest
    22  
    23  import (
    24  	"bytes"
    25  	"reflect"
    26  
    27  	"github.com/m3db/m3/src/m3ninx/doc"
    28  	"github.com/m3db/m3/src/m3ninx/search"
    29  	"github.com/m3db/m3/src/m3ninx/search/query"
    30  
    31  	"github.com/leanovate/gopter"
    32  	"github.com/leanovate/gopter/gen"
    33  )
    34  
    35  // GenAllQuery generates an all query.
    36  func GenAllQuery(docs []doc.Metadata) gopter.Gen {
    37  	return gen.Const(query.NewAllQuery())
    38  }
    39  
    40  // GenFieldQuery generates a field query.
    41  func GenFieldQuery(docs []doc.Metadata) gopter.Gen {
    42  	return func(genParams *gopter.GenParameters) *gopter.GenResult {
    43  		fieldName, _ := fieldNameAndValue(genParams, docs)
    44  		q := query.NewFieldQuery(fieldName)
    45  		return gopter.NewGenResult(q, gopter.NoShrinker)
    46  	}
    47  }
    48  
    49  // GenTermQuery generates a term query.
    50  func GenTermQuery(docs []doc.Metadata) gopter.Gen {
    51  	return func(genParams *gopter.GenParameters) *gopter.GenResult {
    52  		fieldName, fieldValue := fieldNameAndValue(genParams, docs)
    53  		q := query.NewTermQuery(fieldName, fieldValue)
    54  		return gopter.NewGenResult(q, gopter.NoShrinker)
    55  	}
    56  }
    57  
    58  func fieldNameAndValue(genParams *gopter.GenParameters, docs []doc.Metadata) ([]byte, []byte) {
    59  	docIDRes, ok := gen.IntRange(0, len(docs)-1)(genParams).Retrieve()
    60  	if !ok {
    61  		panic("unable to generate term query") // should never happen
    62  	}
    63  	docID := docIDRes.(int)
    64  
    65  	doc := docs[docID]
    66  	fieldRes, ok := gen.IntRange(0, len(doc.Fields)-1)(genParams).Retrieve()
    67  	if !ok {
    68  		panic("unable to generate term query fields") // should never happen
    69  	}
    70  
    71  	fieldID := fieldRes.(int)
    72  	field := doc.Fields[fieldID]
    73  	return field.Name, field.Value
    74  }
    75  
    76  // GenIdenticalTermAndRegexpQuery generates a term query and regexp query with
    77  // the exact same underlying field and pattern.
    78  func GenIdenticalTermAndRegexpQuery(docs []doc.Metadata) gopter.Gen {
    79  	return func(genParams *gopter.GenParameters) *gopter.GenResult {
    80  		fieldName, fieldValue := fieldNameAndValue(genParams, docs)
    81  		termQ := query.NewTermQuery(fieldName, fieldValue)
    82  		regexpQ, err := query.NewRegexpQuery(fieldName, fieldValue)
    83  		if err != nil {
    84  			panic(err)
    85  		}
    86  		return gopter.NewGenResult([]search.Query{termQ, regexpQ}, gopter.NoShrinker)
    87  	}
    88  }
    89  
    90  // GenRegexpQuery generates a regexp query.
    91  func GenRegexpQuery(docs []doc.Metadata) gopter.Gen {
    92  	return func(genParams *gopter.GenParameters) *gopter.GenResult {
    93  		docIDRes, ok := gen.IntRange(0, len(docs)-1)(genParams).Retrieve()
    94  		if !ok {
    95  			panic("unable to generate regexp query") // should never happen
    96  		}
    97  		docID := docIDRes.(int)
    98  
    99  		doc := docs[docID]
   100  		fieldRes, ok := gen.IntRange(0, len(doc.Fields)-1)(genParams).Retrieve()
   101  		if !ok {
   102  			panic("unable to generate regexp query fields") // should never happen
   103  		}
   104  
   105  		fieldID := fieldRes.(int)
   106  		field := doc.Fields[fieldID]
   107  
   108  		var re []byte
   109  
   110  		reType := genParams.NextUint64() % 3
   111  		switch reType {
   112  		case 0: // prefix
   113  			idx := genParams.NextUint64() % uint64(len(field.Value))
   114  			re = append([]byte(nil), field.Value[:idx]...)
   115  			re = append(re, []byte(".*")...)
   116  		case 1: // suffix
   117  			idx := genParams.NextUint64() % uint64(len(field.Value))
   118  			re = append([]byte(".*"), field.Value[idx:]...)
   119  		case 2: // middle
   120  			start := genParams.NextUint64() % uint64(len(field.Value))
   121  			remain := uint64(len(field.Value)) - start
   122  			end := start + genParams.NextUint64()%remain
   123  			re = append(append([]byte(".*"), field.Value[start:end]...), []byte(".*")...)
   124  		}
   125  
   126  		// escape any '(' or ')' we see to avoid regular expression parsing failure
   127  		escapeFront := bytes.Replace(re, []byte("("), []byte("\\("), -1)
   128  		escapeBack := bytes.Replace(escapeFront, []byte(")"), []byte("\\)"), -1)
   129  
   130  		q, err := query.NewRegexpQuery(field.Name, escapeBack)
   131  		if err != nil {
   132  			panic(err)
   133  		}
   134  
   135  		return gopter.NewGenResult(q, gopter.NoShrinker)
   136  	}
   137  }
   138  
   139  // GenNegationQuery generates a negation query.
   140  func GenNegationQuery(docs []doc.Metadata) gopter.Gen {
   141  	return gen.OneGenOf(
   142  		GenFieldQuery(docs),
   143  		GenTermQuery(docs),
   144  		GenRegexpQuery(docs),
   145  	).
   146  		Map(func(q search.Query) search.Query {
   147  			return query.NewNegationQuery(q)
   148  		})
   149  }
   150  
   151  // GenConjunctionQuery generates a conjunction query.
   152  func GenConjunctionQuery(docs []doc.Metadata) gopter.Gen {
   153  	return gen.SliceOf(
   154  		gen.OneGenOf(
   155  			GenFieldQuery(docs),
   156  			GenTermQuery(docs),
   157  			GenRegexpQuery(docs),
   158  			GenNegationQuery(docs)),
   159  		reflect.TypeOf((*search.Query)(nil)).Elem()).
   160  		Map(func(qs []search.Query) search.Query {
   161  			return query.NewConjunctionQuery(qs)
   162  		})
   163  }
   164  
   165  // GenDisjunctionQuery generates a disjunction query.
   166  func GenDisjunctionQuery(docs []doc.Metadata) gopter.Gen {
   167  	return gen.SliceOf(
   168  		gen.OneGenOf(
   169  			GenFieldQuery(docs),
   170  			GenTermQuery(docs),
   171  			GenRegexpQuery(docs),
   172  			GenNegationQuery(docs)),
   173  		reflect.TypeOf((*search.Query)(nil)).Elem()).
   174  		Map(func(qs []search.Query) search.Query {
   175  			return query.NewDisjunctionQuery(qs)
   176  		})
   177  }
   178  
   179  // GenQuery generates a query.
   180  func GenQuery(docs []doc.Metadata) gopter.Gen {
   181  	return gen.OneGenOf(
   182  		GenAllQuery(docs),
   183  		GenFieldQuery(docs),
   184  		GenTermQuery(docs),
   185  		GenRegexpQuery(docs),
   186  		GenNegationQuery(docs),
   187  		GenConjunctionQuery(docs),
   188  		GenDisjunctionQuery(docs))
   189  }