github.com/weaviate/weaviate@v1.24.6/usecases/schema/update.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  	"context"
    16  	"fmt"
    17  	"reflect"
    18  
    19  	"github.com/pkg/errors"
    20  	"github.com/weaviate/weaviate/entities/models"
    21  	"github.com/weaviate/weaviate/entities/schema"
    22  	"github.com/weaviate/weaviate/usecases/replica"
    23  	"github.com/weaviate/weaviate/usecases/sharding"
    24  )
    25  
    26  func (m *Manager) UpdateClass(ctx context.Context, principal *models.Principal,
    27  	className string, updated *models.Class,
    28  ) error {
    29  	m.Lock()
    30  	defer m.Unlock()
    31  
    32  	err := m.Authorizer.Authorize(principal, "update", "schema/objects")
    33  	if err != nil {
    34  		return err
    35  	}
    36  
    37  	initial := m.getClassByName(className)
    38  	if initial == nil {
    39  		return ErrNotFound
    40  	}
    41  	mtEnabled, err := validateUpdatingMT(initial, updated)
    42  	if err != nil {
    43  		return err
    44  	}
    45  
    46  	// make sure unset optionals on 'updated' don't lead to an error, as all
    47  	// optionals would have been set with defaults on the initial already
    48  	m.setClassDefaults(updated)
    49  
    50  	if err := m.validateImmutableFields(initial, updated); err != nil {
    51  		return err
    52  	}
    53  
    54  	// run target vectors validation first, as it will reject classes
    55  	// where legacy vector was changed to target vectors and vice versa
    56  	if err := validateVectorConfigsParityAndImmutables(initial, updated); err != nil {
    57  		return err
    58  	}
    59  	if err := validateVectorIndexConfigImmutableFields(initial, updated); err != nil {
    60  		return err
    61  	}
    62  
    63  	if err := m.validateVectorSettings(updated); err != nil {
    64  		return err
    65  	}
    66  
    67  	if err := m.parseVectorIndexConfig(ctx, updated); err != nil {
    68  		return err
    69  	}
    70  
    71  	if err := m.parseShardingConfig(ctx, updated); err != nil {
    72  		return err
    73  	}
    74  
    75  	if hasTargetVectors(updated) {
    76  		if err := m.migrator.ValidateVectorIndexConfigsUpdate(ctx,
    77  			asVectorIndexConfigs(initial), asVectorIndexConfigs(updated),
    78  		); err != nil {
    79  			return err
    80  		}
    81  	} else {
    82  		if err := m.migrator.ValidateVectorIndexConfigUpdate(ctx,
    83  			initial.VectorIndexConfig.(schema.VectorIndexConfig),
    84  			updated.VectorIndexConfig.(schema.VectorIndexConfig)); err != nil {
    85  			return errors.Wrap(err, "vector index config")
    86  		}
    87  	}
    88  
    89  	if err := m.migrator.ValidateInvertedIndexConfigUpdate(ctx,
    90  		initial.InvertedIndexConfig, updated.InvertedIndexConfig); err != nil {
    91  		return errors.Wrap(err, "inverted index config")
    92  	}
    93  	if err := validateShardingConfig(initial, updated, mtEnabled, m.clusterState); err != nil {
    94  		return fmt.Errorf("validate sharding config: %w", err)
    95  	}
    96  
    97  	if err := replica.ValidateConfigUpdate(initial, updated, m.clusterState); err != nil {
    98  		return fmt.Errorf("replication config: %w", err)
    99  	}
   100  
   101  	updatedSharding := updated.ShardingConfig.(sharding.Config)
   102  	initialRF := initial.ReplicationConfig.Factor
   103  	updatedRF := updated.ReplicationConfig.Factor
   104  	var updatedState *sharding.State
   105  	if initialRF != updatedRF {
   106  		uss, err := m.scaleOut.Scale(ctx, className, updatedSharding, initialRF, updatedRF)
   107  		if err != nil {
   108  			return errors.Wrapf(err, "scale out from %d to %d replicas",
   109  				initialRF, updatedRF)
   110  		}
   111  		updatedState = uss
   112  	}
   113  
   114  	tx, err := m.cluster.BeginTransaction(ctx, UpdateClass,
   115  		UpdateClassPayload{className, updated, updatedState}, DefaultTxTTL)
   116  	if err != nil {
   117  		// possible causes for errors could be nodes down (we expect every node to
   118  		// the up for a schema transaction) or concurrent transactions from other
   119  		// nodes
   120  		return errors.Wrap(err, "open cluster-wide transaction")
   121  	}
   122  
   123  	if err := m.cluster.CommitWriteTransaction(ctx, tx); err != nil {
   124  		return errors.Wrap(err, "commit cluster-wide transaction")
   125  	}
   126  
   127  	return m.updateClassApplyChanges(ctx, className, updated, updatedState)
   128  }
   129  
   130  // validateUpdatingMT validates toggling MT and returns whether mt is enabled
   131  func validateUpdatingMT(current, update *models.Class) (enabled bool, err error) {
   132  	enabled = schema.MultiTenancyEnabled(current)
   133  	if schema.MultiTenancyEnabled(update) != enabled {
   134  		if enabled {
   135  			err = fmt.Errorf("disabling multi-tenancy for an existing class is not supported")
   136  		} else {
   137  			err = fmt.Errorf("enabling multi-tenancy for an existing class is not supported")
   138  		}
   139  	}
   140  	return
   141  }
   142  
   143  func validateShardingConfig(current, update *models.Class, mtEnabled bool, cl clusterState) error {
   144  	if mtEnabled {
   145  		return nil
   146  	}
   147  	first, ok := current.ShardingConfig.(sharding.Config)
   148  	if !ok {
   149  		return fmt.Errorf("current config is not well-formed")
   150  	}
   151  	second, ok := update.ShardingConfig.(sharding.Config)
   152  	if !ok {
   153  		return fmt.Errorf("updated config is not well-formed")
   154  	}
   155  	if err := sharding.ValidateConfigUpdate(first, second, cl); err != nil {
   156  		return err
   157  	}
   158  	return nil
   159  }
   160  
   161  func (m *Manager) updateClassApplyChanges(ctx context.Context, className string,
   162  	updated *models.Class, updatedShardingState *sharding.State,
   163  ) error {
   164  	if updatedShardingState != nil {
   165  		// the sharding state caches the node name, we must therefore set this
   166  		// explicitly now.
   167  		updatedShardingState.SetLocalName(m.clusterState.LocalName())
   168  	}
   169  	if hasTargetVectors(updated) {
   170  		if err := m.migrator.UpdateVectorIndexConfigs(ctx, className, asVectorIndexConfigs(updated)); err != nil {
   171  			return fmt.Errorf("vector index configs update: %w", err)
   172  		}
   173  	} else {
   174  		if err := m.migrator.UpdateVectorIndexConfig(ctx,
   175  			className, updated.VectorIndexConfig.(schema.VectorIndexConfig)); err != nil {
   176  			return fmt.Errorf("vector index config update: %w", err)
   177  		}
   178  	}
   179  	if err := m.migrator.UpdateInvertedIndexConfig(ctx, className,
   180  		updated.InvertedIndexConfig); err != nil {
   181  		return errors.Wrap(err, "inverted index config")
   182  	}
   183  
   184  	if !m.schemaCache.classExist(className) {
   185  		return ErrNotFound
   186  	}
   187  
   188  	payload, err := CreateClassPayload(updated, updatedShardingState)
   189  	if err != nil {
   190  		return err
   191  	}
   192  	payload.ReplaceShards = updatedShardingState != nil
   193  	// can be improved by updating the diff
   194  
   195  	m.schemaCache.updateClass(updated, updatedShardingState)
   196  	m.logger.
   197  		WithField("action", "schema.update_class").
   198  		Debug("saving updated schema to configuration store")
   199  	// payload.Shards
   200  	if err := m.repo.UpdateClass(ctx, payload); err != nil {
   201  		return err
   202  	}
   203  	m.triggerSchemaUpdateCallbacks()
   204  
   205  	return nil
   206  }
   207  
   208  func (m *Manager) validateImmutableFields(initial, updated *models.Class) error {
   209  	immutableFields := []immutableText{
   210  		{
   211  			name:     "class name",
   212  			accessor: func(c *models.Class) string { return c.Class },
   213  		},
   214  	}
   215  
   216  	if err := validateImmutableTextFields(initial, updated, immutableFields...); err != nil {
   217  		return err
   218  	}
   219  
   220  	if !reflect.DeepEqual(initial.Properties, updated.Properties) {
   221  		return errors.Errorf(
   222  			"properties cannot be updated through updating the class. Use the add " +
   223  				"property feature (e.g. \"POST /v1/schema/{className}/properties\") " +
   224  				"to add additional properties")
   225  	}
   226  
   227  	if !reflect.DeepEqual(initial.ModuleConfig, updated.ModuleConfig) {
   228  		return errors.Errorf("module config is immutable")
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  type immutableText struct {
   235  	accessor func(c *models.Class) string
   236  	name     string
   237  }
   238  
   239  func validateImmutableTextFields(previous, next *models.Class,
   240  	immutables ...immutableText,
   241  ) error {
   242  	for _, immutable := range immutables {
   243  		oldField := immutable.accessor(previous)
   244  		newField := immutable.accessor(next)
   245  		if oldField != newField {
   246  			return errors.Errorf("%s is immutable: attempted change from %q to %q",
   247  				immutable.name, oldField, newField)
   248  		}
   249  	}
   250  	return nil
   251  }
   252  
   253  func validateVectorConfigsParityAndImmutables(initial, updated *models.Class) error {
   254  	initialVecCount := len(initial.VectorConfig)
   255  	updatedVecCount := len(updated.VectorConfig)
   256  
   257  	// no cfgs for target vectors
   258  	if initialVecCount == 0 && updatedVecCount == 0 {
   259  		return nil
   260  	}
   261  	// no cfgs for target vectors in initial
   262  	if initialVecCount == 0 && updatedVecCount > 0 {
   263  		return fmt.Errorf("additional configs for vectors")
   264  	}
   265  	// no cfgs for target vectors in updated
   266  	if initialVecCount > 0 && updatedVecCount == 0 {
   267  		return fmt.Errorf("missing configs for vectors")
   268  	}
   269  
   270  	// matching cfgs on both sides
   271  	for vecName := range initial.VectorConfig {
   272  		if _, ok := updated.VectorConfig[vecName]; !ok {
   273  			return fmt.Errorf("missing config for vector %q", vecName)
   274  		}
   275  	}
   276  
   277  	if initialVecCount != updatedVecCount {
   278  		for vecName := range updated.VectorConfig {
   279  			if _, ok := initial.VectorConfig[vecName]; !ok {
   280  				return fmt.Errorf("additional config for vector %q", vecName)
   281  			}
   282  		}
   283  		// fallback, error should be returned in loop
   284  		return fmt.Errorf("number of configs for vectors does not match")
   285  	}
   286  
   287  	// compare matching cfgs
   288  	for vecName, initialCfg := range initial.VectorConfig {
   289  		updatedCfg := updated.VectorConfig[vecName]
   290  
   291  		// immutable vector type
   292  		if initialCfg.VectorIndexType != updatedCfg.VectorIndexType {
   293  			return fmt.Errorf("vector index type of vector %q is immutable: attempted change from %q to %q",
   294  				vecName, initialCfg.VectorIndexType, updatedCfg.VectorIndexType)
   295  		}
   296  
   297  		// immutable vectorizer
   298  		if imap, ok := initialCfg.Vectorizer.(map[string]interface{}); ok && len(imap) == 1 {
   299  			umap, ok := updatedCfg.Vectorizer.(map[string]interface{})
   300  			if !ok || len(umap) != 1 {
   301  				return fmt.Errorf("invalid vectorizer config for vector %q", vecName)
   302  			}
   303  
   304  			ivectorizer := ""
   305  			for k := range imap {
   306  				ivectorizer = k
   307  			}
   308  			uvectorizer := ""
   309  			for k := range umap {
   310  				uvectorizer = k
   311  			}
   312  
   313  			if ivectorizer != uvectorizer {
   314  				return fmt.Errorf("vectorizer of vector %q is immutable: attempted change from %q to %q",
   315  					vecName, ivectorizer, uvectorizer)
   316  			}
   317  		}
   318  	}
   319  	return nil
   320  }
   321  
   322  func validateVectorIndexConfigImmutableFields(initial, updated *models.Class) error {
   323  	return validateImmutableTextFields(initial, updated, []immutableText{
   324  		{
   325  			name:     "vectorizer",
   326  			accessor: func(c *models.Class) string { return c.Vectorizer },
   327  		},
   328  		{
   329  			name:     "vector index type",
   330  			accessor: func(c *models.Class) string { return c.VectorIndexType },
   331  		},
   332  	}...)
   333  }
   334  
   335  func asVectorIndexConfigs(c *models.Class) map[string]schema.VectorIndexConfig {
   336  	if c.VectorConfig == nil {
   337  		return nil
   338  	}
   339  
   340  	cfgs := map[string]schema.VectorIndexConfig{}
   341  	for vecName := range c.VectorConfig {
   342  		cfgs[vecName] = c.VectorConfig[vecName].VectorIndexConfig.(schema.VectorIndexConfig)
   343  	}
   344  	return cfgs
   345  }
   346  
   347  func (m *Manager) UpdateShardStatus(ctx context.Context, principal *models.Principal,
   348  	className, shardName, targetStatus string,
   349  ) error {
   350  	err := m.Authorizer.Authorize(principal, "update",
   351  		fmt.Sprintf("schema/%s/shards/%s", className, shardName))
   352  	if err != nil {
   353  		return err
   354  	}
   355  
   356  	return m.migrator.UpdateShardStatus(ctx, className, shardName, targetStatus)
   357  }