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  }