github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/indexer/index/unique.go (about) 1 // Copyright 2018-2022 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package index 20 21 import ( 22 "context" 23 "os" 24 "path" 25 "path/filepath" 26 "strings" 27 28 idxerrs "github.com/cs3org/reva/v2/pkg/storage/utils/indexer/errors" 29 "github.com/cs3org/reva/v2/pkg/storage/utils/indexer/option" 30 metadata "github.com/cs3org/reva/v2/pkg/storage/utils/metadata" 31 ) 32 33 // Unique are fields for an index of type unique. 34 type Unique struct { 35 caseInsensitive bool 36 indexBy option.IndexBy 37 typeName string 38 filesDir string 39 indexBaseDir string 40 indexRootDir string 41 42 storage metadata.Storage 43 } 44 45 // NewUniqueIndexWithOptions instantiates a new UniqueIndex instance. Init() should be 46 // called afterward to ensure correct on-disk structure. 47 func NewUniqueIndexWithOptions(storage metadata.Storage, o ...option.Option) Index { 48 opts := &option.Options{} 49 for _, opt := range o { 50 opt(opts) 51 } 52 53 u := &Unique{ 54 storage: storage, 55 caseInsensitive: opts.CaseInsensitive, 56 indexBy: opts.IndexBy, 57 typeName: opts.TypeName, 58 filesDir: opts.FilesDir, 59 indexBaseDir: path.Join(opts.Prefix, "index."+storage.Backend()), 60 indexRootDir: path.Join(opts.Prefix, "index."+storage.Backend(), strings.Join([]string{"unique", opts.TypeName, opts.IndexBy.String()}, ".")), 61 } 62 63 return u 64 } 65 66 // Init initializes a unique index. 67 func (idx *Unique) Init() error { 68 if err := idx.storage.MakeDirIfNotExist(context.Background(), idx.indexBaseDir); err != nil { 69 return err 70 } 71 72 return idx.storage.MakeDirIfNotExist(context.Background(), idx.indexRootDir) 73 } 74 75 // Lookup exact lookup by value. 76 func (idx *Unique) Lookup(v string) ([]string, error) { 77 return idx.LookupCtx(context.Background(), v) 78 } 79 80 // LookupCtx retieves multiple exact values and allows passing in a context 81 func (idx *Unique) LookupCtx(ctx context.Context, values ...string) ([]string, error) { 82 var allValues map[string]struct{} 83 if len(values) != 1 { 84 // prefetch all values with one request 85 entries, err := idx.storage.ReadDir(context.Background(), idx.indexRootDir) 86 if err != nil { 87 return nil, err 88 } 89 // convert known values to set 90 allValues = make(map[string]struct{}, len(entries)) 91 for _, e := range entries { 92 allValues[path.Base(e)] = struct{}{} 93 } 94 } 95 96 // convert requested values to set 97 valueSet := make(map[string]struct{}, len(values)) 98 if idx.caseInsensitive { 99 for _, v := range values { 100 valueSet[strings.ToLower(v)] = struct{}{} 101 } 102 } else { 103 for _, v := range values { 104 valueSet[v] = struct{}{} 105 } 106 } 107 108 var matches = make([]string, 0) 109 for v := range valueSet { 110 if _, ok := allValues[v]; ok || len(allValues) == 0 { 111 oldname, err := idx.storage.ResolveSymlink(context.Background(), path.Join(idx.indexRootDir, v)) 112 if err != nil { 113 continue 114 } 115 matches = append(matches, oldname) 116 } 117 } 118 119 if len(matches) == 0 { 120 var v string 121 switch len(values) { 122 case 0: 123 v = "none" 124 case 1: 125 v = values[0] 126 default: 127 v = "multiple" 128 } 129 return nil, &idxerrs.NotFoundErr{TypeName: idx.typeName, IndexBy: idx.indexBy, Value: v} 130 } 131 132 return matches, nil 133 } 134 135 // Add adds a value to the index, returns the path to the root-document 136 func (idx *Unique) Add(id, v string) (string, error) { 137 if v == "" { 138 return "", nil 139 } 140 if idx.caseInsensitive { 141 v = strings.ToLower(v) 142 } 143 target := path.Join(idx.filesDir, id) 144 newName := path.Join(idx.indexRootDir, v) 145 if err := idx.storage.CreateSymlink(context.Background(), target, newName); err != nil { 146 if os.IsExist(err) { 147 return "", &idxerrs.AlreadyExistsErr{TypeName: idx.typeName, IndexBy: idx.indexBy, Value: v} 148 } 149 150 return "", err 151 } 152 153 return newName, nil 154 } 155 156 // Remove a value v from an index. 157 func (idx *Unique) Remove(_ string, v string) error { 158 if v == "" { 159 return nil 160 } 161 if idx.caseInsensitive { 162 v = strings.ToLower(v) 163 } 164 searchPath := path.Join(idx.indexRootDir, v) 165 _, err := idx.storage.ResolveSymlink(context.Background(), searchPath) 166 if err != nil { 167 if os.IsNotExist(err) { 168 err = &idxerrs.NotFoundErr{TypeName: idx.typeName, IndexBy: idx.indexBy, Value: v} 169 } 170 171 return err 172 } 173 174 deletePath := path.Join(idx.indexRootDir, v) 175 return idx.storage.Delete(context.Background(), deletePath) 176 } 177 178 // Update index from <oldV> to <newV>. 179 func (idx *Unique) Update(id, oldV, newV string) error { 180 if idx.caseInsensitive { 181 oldV = strings.ToLower(oldV) 182 newV = strings.ToLower(newV) 183 } 184 185 if err := idx.Remove(id, oldV); err != nil { 186 return err 187 } 188 189 if _, err := idx.Add(id, newV); err != nil { 190 return err 191 } 192 193 return nil 194 } 195 196 // Search allows for glob search on the index. 197 func (idx *Unique) Search(pattern string) ([]string, error) { 198 if idx.caseInsensitive { 199 pattern = strings.ToLower(pattern) 200 } 201 202 paths, err := idx.storage.ReadDir(context.Background(), idx.indexRootDir) 203 if err != nil { 204 return nil, err 205 } 206 207 searchPath := idx.indexRootDir 208 matches := make([]string, 0) 209 for _, p := range paths { 210 if found, err := filepath.Match(pattern, path.Base(p)); found { 211 if err != nil { 212 return nil, err 213 } 214 215 oldPath, err := idx.storage.ResolveSymlink(context.Background(), path.Join(searchPath, path.Base(p))) 216 if err != nil { 217 return nil, err 218 } 219 matches = append(matches, oldPath) 220 } 221 } 222 223 if len(matches) == 0 { 224 return nil, &idxerrs.NotFoundErr{TypeName: idx.typeName, IndexBy: idx.indexBy, Value: pattern} 225 } 226 227 return matches, nil 228 } 229 230 // CaseInsensitive undocumented. 231 func (idx *Unique) CaseInsensitive() bool { 232 return idx.caseInsensitive 233 } 234 235 // IndexBy undocumented. 236 func (idx *Unique) IndexBy() option.IndexBy { 237 return idx.indexBy 238 } 239 240 // TypeName undocumented. 241 func (idx *Unique) TypeName() string { 242 return idx.typeName 243 } 244 245 // FilesDir undocumented. 246 func (idx *Unique) FilesDir() string { 247 return idx.filesDir 248 } 249 250 // Delete deletes the index folder from its storage. 251 func (idx *Unique) Delete() error { 252 return idx.storage.Delete(context.Background(), idx.indexRootDir) 253 }