github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/m3ninx/search/query/conjunction.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 query 22 23 import ( 24 "sort" 25 "strings" 26 27 "github.com/m3db/m3/src/m3ninx/generated/proto/querypb" 28 "github.com/m3db/m3/src/m3ninx/search" 29 "github.com/m3db/m3/src/m3ninx/search/searcher" 30 ) 31 32 // ConjuctionQuery finds documents which match at least one of the given queries. 33 type ConjuctionQuery struct { 34 str string 35 queries []search.Query 36 negations []search.Query 37 } 38 39 // NewConjunctionQuery constructs a new query which matches documents which match all 40 // of the given queries. 41 func NewConjunctionQuery(queries []search.Query) search.Query { 42 qs := make([]search.Query, 0, len(queries)) 43 ns := make([]search.Query, 0, len(queries)) 44 for _, query := range queries { 45 switch query := query.(type) { 46 case *ConjuctionQuery: 47 // Merge conjunction queries into slice of top-level queries. 48 qs = append(qs, query.queries...) 49 continue 50 case *NegationQuery: 51 ns = append(ns, query.query) 52 default: 53 qs = append(qs, query) 54 } 55 } 56 57 if len(qs) == 0 && len(ns) > 0 { 58 // We need to ensure we have at least one non-negation query from which we can calculate 59 // the set difference with the other negation queries. 60 qs = append(qs, queries[0]) 61 ns = ns[1:] 62 } 63 64 // Cause a sort of the queries/negations for deterministic cache key. 65 sort.Slice(qs, func(i, j int) bool { 66 return qs[i].String() < qs[j].String() 67 }) 68 sort.Slice(ns, func(i, j int) bool { 69 return ns[i].String() < ns[j].String() 70 }) 71 72 q := &ConjuctionQuery{ 73 queries: qs, 74 negations: ns, 75 } 76 // NB(r): Calculate string value up front so 77 // not allocated every time String() is called to determine 78 // the cache key. 79 q.str = q.string() 80 return q 81 } 82 83 // Searcher returns a searcher over the provided readers. 84 func (q *ConjuctionQuery) Searcher() (search.Searcher, error) { 85 switch { 86 case len(q.queries) == 0: 87 return searcher.NewEmptySearcher(), nil 88 89 case len(q.queries) == 1 && len(q.negations) == 0: 90 return q.queries[0].Searcher() 91 } 92 93 qsrs := make(search.Searchers, 0, len(q.queries)) 94 for _, q := range q.queries { 95 sr, err := q.Searcher() 96 if err != nil { 97 return nil, err 98 } 99 qsrs = append(qsrs, sr) 100 } 101 102 nsrs := make(search.Searchers, 0, len(q.negations)) 103 for _, q := range q.negations { 104 sr, err := q.Searcher() 105 if err != nil { 106 return nil, err 107 } 108 nsrs = append(nsrs, sr) 109 } 110 111 return searcher.NewConjunctionSearcher(qsrs, nsrs) 112 } 113 114 // Equal reports whether q is equivalent to o. 115 func (q *ConjuctionQuery) Equal(o search.Query) bool { 116 if len(q.queries) == 1 && len(q.negations) == 0 { 117 return q.queries[0].Equal(o) 118 } 119 120 inner, ok := o.(*ConjuctionQuery) 121 if !ok { 122 return false 123 } 124 125 if len(q.queries) != len(inner.queries) { 126 return false 127 } 128 129 if len(q.negations) != len(inner.negations) { 130 return false 131 } 132 133 // TODO: Should order matter? 134 for i := range q.queries { 135 if !q.queries[i].Equal(inner.queries[i]) { 136 return false 137 } 138 } 139 140 for i := range q.negations { 141 if !q.negations[i].Equal(inner.negations[i]) { 142 return false 143 } 144 } 145 146 return true 147 } 148 149 // ToProto returns the Protobuf query struct corresponding to the conjunction query. 150 func (q *ConjuctionQuery) ToProto() *querypb.Query { 151 qs := make([]*querypb.Query, 0, len(q.queries)+len(q.negations)) 152 153 for _, qry := range q.queries { 154 qs = append(qs, qry.ToProto()) 155 } 156 157 for _, qry := range q.negations { 158 neg := NewNegationQuery(qry) 159 qs = append(qs, neg.ToProto()) 160 } 161 162 conj := querypb.ConjunctionQuery{Queries: qs} 163 return &querypb.Query{ 164 Query: &querypb.Query_Conjunction{Conjunction: &conj}, 165 } 166 } 167 168 func (q *ConjuctionQuery) String() string { 169 return q.str 170 } 171 172 func (q *ConjuctionQuery) string() string { 173 var str strings.Builder 174 str.WriteString("conjunction(") 175 join(&str, q.queries) 176 if len(q.negations) > 0 { 177 str.WriteRune(',') 178 joinNegation(&str, q.negations) 179 } 180 str.WriteRune(')') 181 return str.String() 182 }