github.com/weaviate/weaviate@v1.24.6/usecases/schema/cache.go (about) 1 // _ _ 2 // __ _____ __ ___ ___ __ _| |_ ___ 3 // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ 4 // \ V V / __/ (_| |\ V /| | (_| | || __/ 5 // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| 6 // 7 // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. 8 // 9 // CONTACT: hello@weaviate.io 10 // 11 12 package schema 13 14 import ( 15 "errors" 16 "fmt" 17 "sync" 18 19 "github.com/weaviate/weaviate/entities/models" 20 "github.com/weaviate/weaviate/entities/schema" 21 "github.com/weaviate/weaviate/usecases/sharding" 22 ) 23 24 var ( 25 errPropertyNotFound = errors.New("property not found") 26 errClassNotFound = errors.New("class not found") 27 errShardNotFound = errors.New("shard not found") 28 ) 29 30 // State is a cached copy of the schema that can also be saved into a remote 31 // storage, as specified by Repo 32 type State struct { 33 ObjectSchema *models.Schema `json:"object"` 34 ShardingState map[string]*sharding.State 35 } 36 37 // NewState returns a new state with room for nClasses classes 38 func NewState(nClasses int) State { 39 return State{ 40 ObjectSchema: &models.Schema{ 41 Classes: make([]*models.Class, 0, nClasses), 42 }, 43 ShardingState: make(map[string]*sharding.State, nClasses), 44 } 45 } 46 47 type schemaCache struct { 48 sync.RWMutex 49 State 50 } 51 52 // ShardOwner returns the node owner of the specified shard 53 func (s *schemaCache) ShardOwner(class, shard string) (string, error) { 54 s.RLock() 55 defer s.RUnlock() 56 cls := s.ShardingState[class] 57 if cls == nil { 58 return "", errClassNotFound 59 } 60 x, ok := cls.Physical[shard] 61 if !ok { 62 return "", errShardNotFound 63 } 64 if len(x.BelongsToNodes) < 1 || x.BelongsToNodes[0] == "" { 65 return "", fmt.Errorf("owner node not found") 66 } 67 return x.BelongsToNodes[0], nil 68 } 69 70 // ShardReplicas returns the nodes owning shard in class 71 func (s *schemaCache) ShardReplicas(class, shard string) ([]string, error) { 72 s.RLock() 73 defer s.RUnlock() 74 cls := s.ShardingState[class] 75 if cls == nil { 76 return nil, errClassNotFound 77 } 78 x, ok := cls.Physical[shard] 79 if !ok { 80 return nil, errShardNotFound 81 } 82 return x.BelongsToNodes, nil 83 } 84 85 // TenantShard returns shard name for the provided tenant and its activity status 86 func (s *schemaCache) TenantShard(class, tenant string) (string, string) { 87 s.RLock() 88 defer s.RUnlock() 89 ss := s.ShardingState[class] 90 if ss == nil || !ss.PartitioningEnabled { 91 return "", "" 92 } 93 94 if physical, ok := ss.Physical[tenant]; ok { 95 return tenant, physical.ActivityStatus() 96 } 97 return "", "" 98 } 99 100 // ShardFromUUID returns shard name of the provided uuid 101 func (s *schemaCache) ShardFromUUID(class string, uuid []byte) string { 102 s.RLock() 103 defer s.RUnlock() 104 ss := s.ShardingState[class] 105 if ss == nil { 106 return "" 107 } 108 return ss.PhysicalShard(uuid) 109 } 110 111 func (s *schemaCache) CopyShardingState(className string) *sharding.State { 112 s.RLock() 113 defer s.RUnlock() 114 pst := s.ShardingState[className] 115 if pst != nil { 116 st := pst.DeepCopy() 117 pst = &st 118 } 119 120 return pst 121 } 122 123 // LockGuard provides convenient mechanism for owning mutex by function which mutates the state 124 func (s *schemaCache) LockGuard(mutate func()) { 125 s.Lock() 126 defer s.Unlock() 127 mutate() 128 } 129 130 // RLockGuard provides convenient mechanism for owning mutex function which doesn't mutates the state 131 func (s *schemaCache) RLockGuard(reader func() error) error { 132 s.RLock() 133 defer s.RUnlock() 134 return reader() 135 } 136 137 func (s *schemaCache) isEmpty() bool { 138 s.RLock() 139 defer s.RUnlock() 140 return s.State.ObjectSchema == nil || len(s.State.ObjectSchema.Classes) == 0 141 } 142 143 func (s *schemaCache) setState(st State) { 144 s.Lock() 145 defer s.Unlock() 146 s.State = st 147 } 148 149 func (s *schemaCache) detachClass(name string) bool { 150 s.Lock() 151 defer s.Unlock() 152 schema, ci := s.ObjectSchema, -1 153 for i, cls := range schema.Classes { 154 if cls.Class == name { 155 ci = i 156 break 157 } 158 } 159 if ci == -1 { 160 return false 161 } 162 163 nc := len(schema.Classes) 164 schema.Classes[ci] = schema.Classes[nc-1] 165 schema.Classes[nc-1] = nil // to prevent leaking this pointer. 166 schema.Classes = schema.Classes[:nc-1] 167 return true 168 } 169 170 func (s *schemaCache) deleteClassState(name string) { 171 s.Lock() 172 defer s.Unlock() 173 delete(s.ShardingState, name) 174 } 175 176 func (s *schemaCache) unsafeFindClassIf(pred func(*models.Class) bool) *models.Class { 177 for _, cls := range s.ObjectSchema.Classes { 178 if pred(cls) { 179 return cls 180 } 181 } 182 return nil 183 } 184 185 func (s *schemaCache) unsafeFindClass(className string) *models.Class { 186 for _, c := range s.ObjectSchema.Classes { 187 if c.Class == className { 188 return c 189 } 190 } 191 return nil 192 } 193 194 func (s *schemaCache) addClass(c *models.Class, ss *sharding.State) { 195 s.Lock() 196 defer s.Unlock() 197 198 s.ShardingState[c.Class] = ss 199 s.ObjectSchema.Classes = append(s.ObjectSchema.Classes, c) 200 } 201 202 func (s *schemaCache) updateClass(u *models.Class, ss *sharding.State) error { 203 s.Lock() 204 defer s.Unlock() 205 206 if c := s.unsafeFindClass(u.Class); c != nil { 207 *c = *u 208 } else { 209 return errClassNotFound 210 } 211 if ss != nil { 212 s.ShardingState[u.Class] = ss 213 } 214 return nil 215 } 216 217 func (s *schemaCache) addProperty(class string, p *models.Property) (models.Class, error) { 218 s.Lock() 219 defer s.Unlock() 220 221 c := s.unsafeFindClass(class) 222 if c == nil { 223 return models.Class{}, errClassNotFound 224 } 225 226 // update all at once to prevent race condition with concurrent readers 227 src := c.Properties 228 dest := make([]*models.Property, len(src)+1) 229 copy(dest, src) 230 dest[len(src)] = p 231 c.Properties = dest 232 return *c, nil 233 } 234 235 func (s *schemaCache) mergeObjectProperty(class string, p *models.Property) (models.Class, error) { 236 s.Lock() 237 defer s.Unlock() 238 239 c := s.unsafeFindClass(class) 240 if c == nil { 241 return models.Class{}, errClassNotFound 242 } 243 244 var prop *models.Property 245 for i := range c.Properties { 246 if c.Properties[i].Name == p.Name { 247 prop = c.Properties[i] 248 break 249 } 250 } 251 if prop == nil { 252 return models.Class{}, errPropertyNotFound 253 } 254 255 prop.NestedProperties, _ = schema.MergeRecursivelyNestedProperties(prop.NestedProperties, p.NestedProperties) 256 return *c, nil 257 } 258 259 // readOnlySchema returns a read only schema 260 // Changing the schema outside this package might lead to undefined behavior. 261 func (s *schemaCache) readOnlySchema() *models.Schema { 262 s.RLock() 263 defer s.RUnlock() 264 return shallowCopySchema(s.ObjectSchema) 265 } 266 267 func (s *schemaCache) readOnlyClass(name string) (*models.Class, error) { 268 s.RLock() 269 defer s.RUnlock() 270 c := s.unsafeFindClass(name) 271 if c == nil { 272 return nil, errClassNotFound 273 } 274 cp := *c 275 return &cp, nil 276 } 277 278 func (s *schemaCache) classExist(name string) bool { 279 s.RLock() 280 defer s.RUnlock() 281 return s.unsafeFindClass(name) != nil 282 } 283 284 // ShallowCopySchema creates a shallow copy of existing classes 285 // 286 // This function assumes that class attributes are being overwritten. 287 // The properties attribute is the only one that might vary in size; 288 // therefore, we perform a shallow copy of the existing properties. 289 // This implementation assumes that individual properties are overwritten rather than partially updated 290 func shallowCopySchema(m *models.Schema) *models.Schema { 291 cp := *m 292 cp.Classes = make([]*models.Class, len(m.Classes)) 293 for i := 0; i < len(m.Classes); i++ { 294 c := *m.Classes[i] 295 cp.Classes[i] = &c 296 } 297 return &cp 298 }