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 }