github.com/m3db/m3@v1.5.0/src/m3ninx/index/batch.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 index 22 23 import ( 24 "bytes" 25 "errors" 26 "fmt" 27 "sync" 28 29 "github.com/m3db/m3/src/m3ninx/doc" 30 ) 31 32 var ( 33 // ErrDuplicateID is the error returned when a batch contains duplicate IDs. 34 ErrDuplicateID = errors.New("a batch cannot contain duplicate IDs") 35 ) 36 37 // Batch represents a batch of documents that should be inserted into the index. 38 type Batch struct { 39 Docs []doc.Metadata 40 41 // If AllowPartialUpdates is true the index will continue to index documents in the batch 42 // even if it encounters an error attempting to index a previous document in the batch. 43 // If false, on the other hand, then any errors encountered indexing a document will cause 44 // the entire batch to fail and none of the documents in the batch will be indexed. 45 AllowPartialUpdates bool 46 } 47 48 // BatchOption is an option for a Batch. 49 type BatchOption interface { 50 apply(Batch) Batch 51 } 52 53 // batchOptionFunc is an adaptor to allow the use of functions as BatchOptions. 54 type batchOptionFunc func(Batch) Batch 55 56 func (f batchOptionFunc) apply(b Batch) Batch { 57 return f(b) 58 } 59 60 // AllowPartialUpdates permits an index to continue indexing documents in a batch even if 61 // it encountered an error inserting a prior document. 62 func AllowPartialUpdates() BatchOption { 63 return batchOptionFunc(func(b Batch) Batch { 64 b.AllowPartialUpdates = true 65 return b 66 }) 67 } 68 69 // NewBatch returns a Batch of documents. 70 func NewBatch(docs []doc.Metadata, opts ...BatchOption) Batch { 71 b := Batch{Docs: docs} 72 73 for _, opt := range opts { 74 b = opt.apply(b) 75 } 76 77 return b 78 } 79 80 // BatchPartialError indicates an error was encountered inserting some documents in a batch. 81 // It is not safe for concurrent use. 82 type BatchPartialError struct { 83 sync.Mutex 84 85 errs []BatchError 86 } 87 88 // BatchError is an error that occurred for a document being inserted. 89 type BatchError struct { 90 Err error 91 Idx int 92 } 93 94 // NewBatchPartialError returns a new BatchPartialError. 95 func NewBatchPartialError() *BatchPartialError { 96 return &BatchPartialError{} 97 } 98 99 func (e *BatchPartialError) Error() string { 100 var b bytes.Buffer 101 for i := range e.errs { 102 b.WriteString(fmt.Sprintf("failed to insert document at index %v in batch: %v", 103 e.errs[i].Idx, e.errs[i].Err)) 104 if i != len(e.errs)-1 { 105 b.WriteString("\n") 106 } 107 } 108 return b.String() 109 } 110 111 // FilterDuplicateIDErrors returns a new BatchPartialError (or nil), without any DuplicateIDError(s). 112 // NB(prateek): it mutates the order of errors in the original error to avoid allocations. 113 // NB(bodu): we return an `error` here since go does not evaluate `nil` errors correctly when 114 // we return a custom type (*BatchPartialError) here and cast it to `error`. 115 func (e *BatchPartialError) FilterDuplicateIDErrors() error { 116 // cheap to do the copy as it's just pointers for the slices 117 var ( 118 errs = e.errs 119 i = 0 120 ) 121 122 for i < len(errs) { 123 if errs[i].Err == ErrDuplicateID { 124 errs[i], errs[len(errs)-1] = errs[len(errs)-1], errs[i] 125 errs = errs[:len(errs)-1] 126 continue 127 } 128 i++ 129 } 130 if len(errs) == 0 { 131 return nil 132 } 133 return &BatchPartialError{errs: errs} 134 } 135 136 // Add adds an error to e. Any nil errors are ignored. 137 func (e *BatchPartialError) Add(err BatchError) { 138 if err.Err == nil { 139 return 140 } 141 e.errs = append(e.errs, err) 142 } 143 144 // AddWithLock adds an error to e with a lock. Any nil errors are ignored. 145 func (e *BatchPartialError) AddWithLock(err BatchError) { 146 if err.Err == nil { 147 return 148 } 149 e.Lock() 150 e.errs = append(e.errs, err) 151 e.Unlock() 152 } 153 154 // Errs returns the errors with the indexes of the documents in the batch 155 // which were not indexed. 156 func (e *BatchPartialError) Errs() []BatchError { 157 return e.errs 158 } 159 160 // IsEmpty returns a bool indicating whether e is empty or not. 161 func (e *BatchPartialError) IsEmpty() bool { 162 return len(e.errs) == 0 163 } 164 165 // IsBatchPartialError returns a bool indicating whether err is a BatchPartialError or not. 166 func IsBatchPartialError(err error) bool { 167 _, ok := err.(*BatchPartialError) 168 return ok 169 }