github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/set_zone_config.go (about) 1 // Copyright 2017 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package sql 12 13 import ( 14 "context" 15 "fmt" 16 "sort" 17 "strings" 18 19 "github.com/cockroachdb/cockroach/pkg/config" 20 "github.com/cockroachdb/cockroach/pkg/config/zonepb" 21 "github.com/cockroachdb/cockroach/pkg/keys" 22 "github.com/cockroachdb/cockroach/pkg/kv" 23 "github.com/cockroachdb/cockroach/pkg/roachpb" 24 "github.com/cockroachdb/cockroach/pkg/server/serverpb" 25 "github.com/cockroachdb/cockroach/pkg/server/telemetry" 26 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 27 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 28 "github.com/cockroachdb/cockroach/pkg/sql/privilege" 29 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 30 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 31 "github.com/cockroachdb/cockroach/pkg/sql/sqltelemetry" 32 "github.com/cockroachdb/cockroach/pkg/sql/types" 33 "github.com/cockroachdb/cockroach/pkg/util/protoutil" 34 "github.com/cockroachdb/errors" 35 "github.com/gogo/protobuf/proto" 36 "gopkg.in/yaml.v2" 37 ) 38 39 type optionValue struct { 40 inheritValue bool 41 explicitValue tree.TypedExpr 42 } 43 44 type setZoneConfigNode struct { 45 zoneSpecifier tree.ZoneSpecifier 46 allIndexes bool 47 yamlConfig tree.TypedExpr 48 options map[tree.Name]optionValue 49 setDefault bool 50 51 run setZoneConfigRun 52 } 53 54 // supportedZoneConfigOptions indicates how to translate SQL variable 55 // assignments in ALTER CONFIGURE ZONE to assignments to the member 56 // fields of zonepb.ZoneConfig. 57 var supportedZoneConfigOptions = map[tree.Name]struct { 58 requiredType *types.T 59 setter func(*zonepb.ZoneConfig, tree.Datum) 60 }{ 61 "range_min_bytes": {types.Int, func(c *zonepb.ZoneConfig, d tree.Datum) { c.RangeMinBytes = proto.Int64(int64(tree.MustBeDInt(d))) }}, 62 "range_max_bytes": {types.Int, func(c *zonepb.ZoneConfig, d tree.Datum) { c.RangeMaxBytes = proto.Int64(int64(tree.MustBeDInt(d))) }}, 63 "num_replicas": {types.Int, func(c *zonepb.ZoneConfig, d tree.Datum) { c.NumReplicas = proto.Int32(int32(tree.MustBeDInt(d))) }}, 64 "gc.ttlseconds": {types.Int, func(c *zonepb.ZoneConfig, d tree.Datum) { 65 c.GC = &zonepb.GCPolicy{TTLSeconds: int32(tree.MustBeDInt(d))} 66 }}, 67 "constraints": {types.String, func(c *zonepb.ZoneConfig, d tree.Datum) { 68 constraintsList := zonepb.ConstraintsList{ 69 Constraints: c.Constraints, 70 Inherited: c.InheritedConstraints, 71 } 72 loadYAML(&constraintsList, string(tree.MustBeDString(d))) 73 c.Constraints = constraintsList.Constraints 74 c.InheritedConstraints = false 75 }}, 76 "lease_preferences": {types.String, func(c *zonepb.ZoneConfig, d tree.Datum) { 77 loadYAML(&c.LeasePreferences, string(tree.MustBeDString(d))) 78 c.InheritedLeasePreferences = false 79 }}, 80 } 81 82 // zoneOptionKeys contains the keys from suportedZoneConfigOptions in 83 // deterministic order. Needed to make the event log output 84 // deterministic. 85 var zoneOptionKeys = func() []string { 86 l := make([]string, 0, len(supportedZoneConfigOptions)) 87 for k := range supportedZoneConfigOptions { 88 l = append(l, string(k)) 89 } 90 sort.Strings(l) 91 return l 92 }() 93 94 func loadYAML(dst interface{}, yamlString string) { 95 if err := yaml.UnmarshalStrict([]byte(yamlString), dst); err != nil { 96 panic(err) 97 } 98 } 99 100 func (p *planner) SetZoneConfig(ctx context.Context, n *tree.SetZoneConfig) (planNode, error) { 101 if err := checkPrivilegeForSetZoneConfig(ctx, p, n.ZoneSpecifier); err != nil { 102 return nil, err 103 } 104 105 var yamlConfig tree.TypedExpr 106 107 if n.YAMLConfig != nil { 108 // We have a CONFIGURE ZONE = <expr> assignment. 109 // This can be either a literal NULL (deletion), or a string containing YAML. 110 // We also support byte arrays for backward compatibility with 111 // previous versions of CockroachDB. 112 113 var err error 114 yamlConfig, err = p.analyzeExpr( 115 ctx, n.YAMLConfig, nil, tree.IndexedVarHelper{}, types.String, false /*requireType*/, "configure zone") 116 if err != nil { 117 return nil, err 118 } 119 120 switch typ := yamlConfig.ResolvedType(); typ.Family() { 121 case types.UnknownFamily: 122 // Unknown occurs if the user entered a literal NULL. That's OK and will mean deletion. 123 case types.StringFamily: 124 case types.BytesFamily: 125 default: 126 return nil, pgerror.Newf(pgcode.InvalidParameterValue, 127 "zone config must be of type string or bytes, not %s", typ) 128 } 129 } 130 131 var options map[tree.Name]optionValue 132 if n.Options != nil { 133 // We have a CONFIGURE ZONE USING ... assignment. 134 // Here we are constrained by the supported ZoneConfig fields, 135 // as described by supportedZoneConfigOptions above. 136 137 options = make(map[tree.Name]optionValue) 138 for _, opt := range n.Options { 139 if _, alreadyExists := options[opt.Key]; alreadyExists { 140 return nil, pgerror.Newf(pgcode.InvalidParameterValue, 141 "duplicate zone config parameter: %q", tree.ErrString(&opt.Key)) 142 } 143 req, ok := supportedZoneConfigOptions[opt.Key] 144 if !ok { 145 return nil, pgerror.Newf(pgcode.InvalidParameterValue, 146 "unsupported zone config parameter: %q", tree.ErrString(&opt.Key)) 147 } 148 telemetry.Inc( 149 sqltelemetry.SchemaSetZoneConfigCounter( 150 n.ZoneSpecifier.TelemetryName(), 151 string(opt.Key), 152 ), 153 ) 154 if opt.Value == nil { 155 options[opt.Key] = optionValue{inheritValue: true, explicitValue: nil} 156 continue 157 } 158 valExpr, err := p.analyzeExpr( 159 ctx, opt.Value, nil, tree.IndexedVarHelper{}, req.requiredType, true /*requireType*/, string(opt.Key)) 160 if err != nil { 161 return nil, err 162 } 163 options[opt.Key] = optionValue{inheritValue: false, explicitValue: valExpr} 164 } 165 } 166 167 return &setZoneConfigNode{ 168 zoneSpecifier: n.ZoneSpecifier, 169 allIndexes: n.AllIndexes, 170 yamlConfig: yamlConfig, 171 options: options, 172 setDefault: n.SetDefault, 173 }, nil 174 } 175 176 func checkPrivilegeForSetZoneConfig(ctx context.Context, p *planner, zs tree.ZoneSpecifier) error { 177 // For system ranges, the system database, or system tables, the user must be 178 // an admin. Otherwise we require CREATE privileges on the database or table 179 // in question. 180 if zs.NamedZone != "" { 181 return p.RequireAdminRole(ctx, "alter system ranges") 182 } 183 if zs.Database != "" { 184 if zs.Database == "system" { 185 return p.RequireAdminRole(ctx, "alter the system database") 186 } 187 dbDesc, err := p.ResolveUncachedDatabaseByName(ctx, string(zs.Database), true) 188 if err != nil { 189 return err 190 } 191 dbCreatePrivilegeErr := p.CheckPrivilege(ctx, dbDesc, privilege.CREATE) 192 dbZoneConfigPrivilegeErr := p.CheckPrivilege(ctx, dbDesc, privilege.ZONECONFIG) 193 194 // Can set ZoneConfig if user has either CREATE privilege or ZONECONFIG privilege at the Database level 195 if dbZoneConfigPrivilegeErr == nil || dbCreatePrivilegeErr == nil { 196 return nil 197 } 198 199 return pgerror.Newf(pgcode.InsufficientPrivilege, 200 "user %s does not have %s or %s privilege on %s %s", 201 p.SessionData().User, privilege.ZONECONFIG, privilege.CREATE, dbDesc.TypeName(), dbDesc.GetName()) 202 } 203 tableDesc, err := p.resolveTableForZone(ctx, &zs) 204 if err != nil { 205 if zs.TargetsIndex() && zs.TableOrIndex.Table.ObjectName == "" { 206 err = errors.WithHint(err, "try specifying the index as <tablename>@<indexname>") 207 } 208 return err 209 } 210 if tableDesc.ParentID == keys.SystemDatabaseID { 211 return p.RequireAdminRole(ctx, "alter system tables") 212 } 213 214 // Can set ZoneConfig if user has either CREATE privilege or ZONECONFIG privilege at the Table level 215 tableCreatePrivilegeErr := p.CheckPrivilege(ctx, tableDesc, privilege.CREATE) 216 tableZoneConfigPrivilegeErr := p.CheckPrivilege(ctx, tableDesc, privilege.ZONECONFIG) 217 218 if tableCreatePrivilegeErr == nil || tableZoneConfigPrivilegeErr == nil { 219 return nil 220 } 221 222 return pgerror.Newf(pgcode.InsufficientPrivilege, 223 "user %s does not have %s or %s privilege on %s %s", 224 p.SessionData().User, privilege.ZONECONFIG, privilege.CREATE, tableDesc.TypeName(), tableDesc.GetName()) 225 } 226 227 // setZoneConfigRun contains the run-time state of setZoneConfigNode during local execution. 228 type setZoneConfigRun struct { 229 numAffected int 230 } 231 232 // ReadingOwnWrites implements the planNodeReadingOwnWrites interface. 233 // This is because CONFIGURE ZONE performs multiple KV operations on descriptors 234 // and expects to see its own writes. 235 func (n *setZoneConfigNode) ReadingOwnWrites() {} 236 237 func (n *setZoneConfigNode) startExec(params runParams) error { 238 var yamlConfig string 239 var setters []func(c *zonepb.ZoneConfig) 240 deleteZone := false 241 242 // Evaluate the configuration input. 243 if n.yamlConfig != nil { 244 // From a YAML string. 245 datum, err := n.yamlConfig.Eval(params.EvalContext()) 246 if err != nil { 247 return err 248 } 249 switch val := datum.(type) { 250 case *tree.DString: 251 yamlConfig = string(*val) 252 case *tree.DBytes: 253 yamlConfig = string(*val) 254 default: 255 deleteZone = true 256 } 257 // Trim spaces, to detect empty zonfigurations. 258 // We'll add back the missing newline below. 259 yamlConfig = strings.TrimSpace(yamlConfig) 260 } 261 var optionStr strings.Builder 262 var copyFromParentList []tree.Name 263 if n.options != nil { 264 // Set from var = value attributes. 265 // 266 // We iterate over zoneOptionKeys instead of iterating over 267 // n.options directly so that the optionStr string constructed for 268 // the event log remains deterministic. 269 for i := range zoneOptionKeys { 270 name := (*tree.Name)(&zoneOptionKeys[i]) 271 val, ok := n.options[*name] 272 if !ok { 273 continue 274 } 275 // We don't add the setters for the fields that will copy values 276 // from the parents. These fields will be set by taking what 277 // value would apply to the zone and setting that value explicitly. 278 // Instead we add the fields to a list that we use at a later time 279 // to copy values over. 280 inheritVal, expr := val.inheritValue, val.explicitValue 281 if inheritVal { 282 copyFromParentList = append(copyFromParentList, *name) 283 if optionStr.Len() > 0 { 284 optionStr.WriteString(", ") 285 } 286 fmt.Fprintf(&optionStr, "%s = COPY FROM PARENT", name) 287 continue 288 } 289 datum, err := expr.Eval(params.EvalContext()) 290 if err != nil { 291 return err 292 } 293 if datum == tree.DNull { 294 return pgerror.Newf(pgcode.InvalidParameterValue, 295 "unsupported NULL value for %q", tree.ErrString(name)) 296 } 297 setter := supportedZoneConfigOptions[*name].setter 298 setters = append(setters, func(c *zonepb.ZoneConfig) { setter(c, datum) }) 299 if optionStr.Len() > 0 { 300 optionStr.WriteString(", ") 301 } 302 fmt.Fprintf(&optionStr, "%s = %s", name, datum) 303 304 } 305 } 306 307 telemetry.Inc( 308 sqltelemetry.SchemaChangeAlterCounterWithExtra(n.zoneSpecifier.TelemetryName(), "configure_zone"), 309 ) 310 311 // If the specifier is for a table, partition or index, this will 312 // resolve the table descriptor. If the specifier is for a database 313 // or range, this is a no-op and a nil pointer is returned as 314 // descriptor. 315 table, err := params.p.resolveTableForZone(params.ctx, &n.zoneSpecifier) 316 if err != nil { 317 return err 318 } 319 320 if n.zoneSpecifier.TargetsPartition() && len(n.zoneSpecifier.TableOrIndex.Index) == 0 && !n.allIndexes { 321 // Backward compatibility for ALTER PARTITION ... OF TABLE. Determine which 322 // index has the specified partition. 323 partitionName := string(n.zoneSpecifier.Partition) 324 indexes := table.FindIndexesWithPartition(partitionName) 325 switch len(indexes) { 326 case 0: 327 return fmt.Errorf("partition %q does not exist on table %q", partitionName, table.Name) 328 case 1: 329 n.zoneSpecifier.TableOrIndex.Index = tree.UnrestrictedName(indexes[0].Name) 330 default: 331 err := fmt.Errorf( 332 "partition %q exists on multiple indexes of table %q", partitionName, table.Name) 333 err = pgerror.WithCandidateCode(err, pgcode.InvalidParameterValue) 334 err = errors.WithHint(err, "try ALTER PARTITION ... OF INDEX ...") 335 return err 336 } 337 } 338 339 // If this is an ALTER ALL PARTITIONS statement, we need to find all indexes 340 // with the specified partition name and apply the zone configuration to all 341 // of them. 342 var specifiers []tree.ZoneSpecifier 343 if n.zoneSpecifier.TargetsPartition() && n.allIndexes { 344 sqltelemetry.IncrementPartitioningCounter(sqltelemetry.AlterAllPartitions) 345 for _, idx := range table.AllNonDropIndexes() { 346 if p := idx.FindPartitionByName(string(n.zoneSpecifier.Partition)); p != nil { 347 zs := n.zoneSpecifier 348 zs.TableOrIndex.Index = tree.UnrestrictedName(idx.Name) 349 specifiers = append(specifiers, zs) 350 } 351 } 352 } else { 353 specifiers = append(specifiers, n.zoneSpecifier) 354 } 355 356 applyZoneConfig := func(zs tree.ZoneSpecifier) error { 357 subzonePlaceholder := false 358 // resolveZone determines the ID of the target object of the zone 359 // specifier. This ought to succeed regardless of whether there is 360 // already a zone config for the target object. 361 targetID, err := resolveZone(params.ctx, params.p.txn, &zs) 362 if err != nil { 363 return err 364 } 365 // NamespaceTableID is not in the system gossip range, but users should not 366 // be allowed to set zone configs on it. 367 if targetID != keys.SystemDatabaseID && sqlbase.IsSystemConfigID(targetID) || targetID == keys.NamespaceTableID { 368 return pgerror.Newf(pgcode.CheckViolation, 369 `cannot set zone configs for system config tables; `+ 370 `try setting your config on the entire "system" database instead`) 371 } else if targetID == keys.RootNamespaceID && deleteZone { 372 return pgerror.Newf(pgcode.CheckViolation, 373 "cannot remove default zone") 374 } 375 376 // resolveSubzone determines the sub-parts of the zone 377 // specifier. This ought to succeed regardless of whether there is 378 // already a zone config. 379 index, partition, err := resolveSubzone(&zs, table) 380 if err != nil { 381 return err 382 } 383 384 // Retrieve the partial zone configuration 385 partialZone, err := getZoneConfigRaw(params.ctx, params.p.txn, targetID) 386 if err != nil { 387 return err 388 } 389 390 // No zone was found. Possibly a SubzonePlaceholder depending on the index. 391 if partialZone == nil { 392 partialZone = zonepb.NewZoneConfig() 393 if index != nil { 394 subzonePlaceholder = true 395 } 396 } 397 398 var partialSubzone *zonepb.Subzone 399 if index != nil { 400 partialSubzone = partialZone.GetSubzoneExact(uint32(index.ID), partition) 401 if partialSubzone == nil { 402 partialSubzone = &zonepb.Subzone{Config: *zonepb.NewZoneConfig()} 403 } 404 } 405 406 // Retrieve the zone configuration. 407 // 408 // If the statement was USING DEFAULT, we want to ignore the zone 409 // config that exists on targetID and instead skip to the inherited 410 // default (whichever applies -- a database if targetID is a table, 411 // default if targetID is a database, etc.). For this, we use the last 412 // parameter getInheritedDefault to GetZoneConfigInTxn(). 413 // These zones are only used for validations. The merged zone is will 414 // not be written. 415 _, completeZone, completeSubzone, err := GetZoneConfigInTxn(params.ctx, params.p.txn, 416 uint32(targetID), index, partition, n.setDefault) 417 418 if errors.Is(err, errNoZoneConfigApplies) { 419 // No zone config yet. 420 // 421 // GetZoneConfigInTxn will fail with errNoZoneConfigApplies when 422 // the target ID is not a database object, i.e. one of the system 423 // ranges (liveness, meta, etc.), and did not have a zone config 424 // already. 425 completeZone = protoutil.Clone( 426 params.extendedEvalCtx.ExecCfg.DefaultZoneConfig).(*zonepb.ZoneConfig) 427 } else if err != nil { 428 return err 429 } 430 431 // We need to inherit zone configuration information from the correct zone, 432 // not completeZone. 433 { 434 // Function for getting the zone config within the current transaction. 435 getKey := func(key roachpb.Key) (*roachpb.Value, error) { 436 kv, err := params.p.txn.Get(params.ctx, key) 437 if err != nil { 438 return nil, err 439 } 440 return kv.Value, nil 441 } 442 if index == nil { 443 // If we are operating on a zone, get all fields that the zone would 444 // inherit from its parent. We do this by using an empty zoneConfig 445 // and completing at the level of the current zone. 446 zoneInheritedFields := zonepb.ZoneConfig{} 447 if err := completeZoneConfig(&zoneInheritedFields, uint32(targetID), getKey); err != nil { 448 return err 449 } 450 partialZone.CopyFromZone(zoneInheritedFields, copyFromParentList) 451 } else { 452 // If we are operating on a subZone, we need to inherit all remaining 453 // unset fields in its parent zone, which is partialZone. 454 zoneInheritedFields := *partialZone 455 if err := completeZoneConfig(&zoneInheritedFields, uint32(targetID), getKey); err != nil { 456 return err 457 } 458 // In the case we have just an index, we should copy from the inherited 459 // zone's fields (whether that was the table or database). 460 if partition == "" { 461 partialSubzone.Config.CopyFromZone(zoneInheritedFields, copyFromParentList) 462 } else { 463 // In the case of updating a partition, we need try inheriting fields 464 // from the subzone's index, and inherit the remainder from the zone. 465 subzoneInheritedFields := zonepb.ZoneConfig{} 466 if indexSubzone := completeZone.GetSubzone(uint32(index.ID), ""); indexSubzone != nil { 467 subzoneInheritedFields.InheritFromParent(&indexSubzone.Config) 468 } 469 subzoneInheritedFields.InheritFromParent(&zoneInheritedFields) 470 // After inheriting fields, copy the requested ones into the 471 // partialSubzone.Config. 472 partialSubzone.Config.CopyFromZone(subzoneInheritedFields, copyFromParentList) 473 } 474 } 475 } 476 477 if deleteZone { 478 if index != nil { 479 didDelete := completeZone.DeleteSubzone(uint32(index.ID), partition) 480 _ = partialZone.DeleteSubzone(uint32(index.ID), partition) 481 if !didDelete { 482 // If we didn't do any work, return early. We'd otherwise perform an 483 // update that would make it look like one row was affected. 484 return nil 485 } 486 } else { 487 completeZone.DeleteTableConfig() 488 partialZone.DeleteTableConfig() 489 } 490 } else { 491 // Validate the user input. 492 if len(yamlConfig) == 0 || yamlConfig[len(yamlConfig)-1] != '\n' { 493 // YAML values must always end with a newline character. If there is none, 494 // for UX convenience add one. 495 yamlConfig += "\n" 496 } 497 498 // Determine where to load the configuration. 499 newZone := *completeZone 500 if completeSubzone != nil { 501 newZone = completeSubzone.Config 502 } 503 504 // Determine where to load the partial configuration. 505 // finalZone is where the new changes are unmarshalled onto. 506 // It must be a fresh ZoneConfig if a new subzone is being created. 507 // If an existing subzone is being modified, finalZone is overridden. 508 finalZone := *partialZone 509 if partialSubzone != nil { 510 finalZone = partialSubzone.Config 511 } 512 513 // ALTER RANGE default USING DEFAULT sets the default to the in 514 // memory default value. 515 if n.setDefault && keys.RootNamespaceID == uint32(targetID) { 516 finalZone = *protoutil.Clone( 517 params.extendedEvalCtx.ExecCfg.DefaultZoneConfig).(*zonepb.ZoneConfig) 518 } else if n.setDefault { 519 finalZone = *zonepb.NewZoneConfig() 520 } 521 // Load settings from YAML. If there was no YAML (e.g. because the 522 // query specified CONFIGURE ZONE USING), the YAML string will be 523 // empty, in which case the unmarshaling will be a no-op. This is 524 // innocuous. 525 if err := yaml.UnmarshalStrict([]byte(yamlConfig), &newZone); err != nil { 526 return pgerror.Newf(pgcode.CheckViolation, 527 "could not parse zone config: %v", err) 528 } 529 530 // Load settings from YAML into the partial zone as well. 531 if err := yaml.UnmarshalStrict([]byte(yamlConfig), &finalZone); err != nil { 532 return pgerror.Newf(pgcode.CheckViolation, 533 "could not parse zone config: %v", err) 534 } 535 536 // Load settings from var = val assignments. If there were no such 537 // settings, (e.g. because the query specified CONFIGURE ZONE = or 538 // USING DEFAULT), the setter slice will be empty and this will be 539 // a no-op. This is innocuous. 540 for _, setter := range setters { 541 // A setter may fail with an error-via-panic. Catch those. 542 if err := func() (err error) { 543 defer func() { 544 if p := recover(); p != nil { 545 if errP, ok := p.(error); ok { 546 // Catch and return the error. 547 err = errP 548 } else { 549 // Nothing we know about, let it continue as a panic. 550 panic(p) 551 } 552 } 553 }() 554 555 setter(&newZone) 556 setter(&finalZone) 557 return nil 558 }(); err != nil { 559 return err 560 } 561 } 562 563 // Validate that there are no conflicts in the zone setup. 564 if err := validateNoRepeatKeysInZone(&newZone); err != nil { 565 return err 566 } 567 568 ss, err := params.extendedEvalCtx.StatusServer.OptionalErr() 569 if err != nil { 570 return err 571 } 572 573 // Validate that the result makes sense. 574 if err := validateZoneAttrsAndLocalities( 575 params.ctx, 576 ss.Nodes, 577 &newZone, 578 ); err != nil { 579 return err 580 } 581 582 // Are we operating on an index? 583 if index == nil { 584 // No: the final zone config is the one we just processed. 585 completeZone = &newZone 586 partialZone = &finalZone 587 // Since we are writing to a zone that is not a subzone, we need to 588 // make sure that the zone config is not considered a placeholder 589 // anymore. If the settings applied to this zone don't touch the 590 // NumReplicas field, set it to nil so that the zone isn't considered a 591 // placeholder anymore. 592 if partialZone.IsSubzonePlaceholder() { 593 partialZone.NumReplicas = nil 594 } 595 } else { 596 // If the zone config for targetID was a subzone placeholder, it'll have 597 // been skipped over by GetZoneConfigInTxn. We need to load it regardless 598 // to avoid blowing away other subzones. 599 600 // TODO(ridwanmsharif): How is this supposed to change? getZoneConfigRaw 601 // gives no guarantees about completeness. Some work might need to happen 602 // here to complete the missing fields. The reason is because we don't know 603 // here if a zone is a placeholder or not. Can we do a GetConfigInTxn here? 604 // And if it is a placeholder, we use getZoneConfigRaw to create one. 605 completeZone, err = getZoneConfigRaw(params.ctx, params.p.txn, targetID) 606 if err != nil { 607 return err 608 } else if completeZone == nil { 609 completeZone = zonepb.NewZoneConfig() 610 } 611 completeZone.SetSubzone(zonepb.Subzone{ 612 IndexID: uint32(index.ID), 613 PartitionName: partition, 614 Config: newZone, 615 }) 616 617 // The partial zone might just be empty. If so, 618 // replace it with a SubzonePlaceholder. 619 if subzonePlaceholder { 620 partialZone.DeleteTableConfig() 621 } 622 623 partialZone.SetSubzone(zonepb.Subzone{ 624 IndexID: uint32(index.ID), 625 PartitionName: partition, 626 Config: finalZone, 627 }) 628 } 629 630 // Finally revalidate everything. Validate only the completeZone config. 631 if err := completeZone.Validate(); err != nil { 632 return pgerror.Newf(pgcode.CheckViolation, 633 "could not validate zone config: %v", err) 634 } 635 636 // Finally check for the extra protection partial zone configs would 637 // require from changes made to parent zones. The extra protections are: 638 // 639 // RangeMinBytes and RangeMaxBytes must be set together 640 // LeasePreferences cannot be set unless Constraints are explicitly set 641 // Per-replica constraints cannot be set unless num_replicas is explicitly set 642 if err := finalZone.ValidateTandemFields(); err != nil { 643 err = errors.Wrap(err, "could not validate zone config") 644 err = pgerror.WithCandidateCode(err, pgcode.InvalidParameterValue) 645 err = errors.WithHint(err, 646 "try ALTER ... CONFIGURE ZONE USING <field_name> = COPY FROM PARENT [, ...] to populate the field") 647 return err 648 } 649 } 650 651 // Write the partial zone configuration. 652 hasNewSubzones := !deleteZone && index != nil 653 execConfig := params.extendedEvalCtx.ExecCfg 654 zoneToWrite := partialZone 655 656 n.run.numAffected, err = writeZoneConfig(params.ctx, params.p.txn, 657 targetID, table, zoneToWrite, execConfig, hasNewSubzones) 658 if err != nil { 659 return err 660 } 661 662 // Record that the change has occurred for auditing. 663 var eventLogType EventLogType 664 info := struct { 665 Target string 666 Config string `json:",omitempty"` 667 Options string `json:",omitempty"` 668 User string 669 }{ 670 Target: tree.AsStringWithFQNames(&zs, params.Ann()), 671 Config: strings.TrimSpace(yamlConfig), 672 Options: optionStr.String(), 673 User: params.SessionData().User, 674 } 675 if deleteZone { 676 eventLogType = EventLogRemoveZoneConfig 677 } else { 678 eventLogType = EventLogSetZoneConfig 679 } 680 return MakeEventLogger(params.extendedEvalCtx.ExecCfg).InsertEventRecord( 681 params.ctx, 682 params.p.txn, 683 eventLogType, 684 int32(targetID), 685 int32(params.extendedEvalCtx.NodeID.SQLInstanceID()), 686 info, 687 ) 688 } 689 for _, zs := range specifiers { 690 // Note(solon): Currently the zone configurations are applied serially for 691 // each specifier. This could certainly be made more efficient. For 692 // instance, we should only need to write to the system.zones table once 693 // rather than once for every specifier. However, the number of specifiers 694 // is expected to be low--it's bounded by the number of indexes on the 695 // table--so I'm holding off on adding that complexity unless we find it's 696 // necessary. 697 if err := applyZoneConfig(zs); err != nil { 698 return err 699 } 700 } 701 return nil 702 } 703 704 func (n *setZoneConfigNode) Next(runParams) (bool, error) { return false, nil } 705 func (n *setZoneConfigNode) Values() tree.Datums { return nil } 706 func (*setZoneConfigNode) Close(context.Context) {} 707 708 func (n *setZoneConfigNode) FastPathResults() (int, bool) { return n.run.numAffected, true } 709 710 type nodeGetter func(context.Context, *serverpb.NodesRequest) (*serverpb.NodesResponse, error) 711 712 // Check that there are not duplicated values for a particular 713 // constraint. For example, constraints [+region=us-east1,+region=us-east2] 714 // will be rejected. Additionally, invalid constraints such as 715 // [+region=us-east1, -region=us-east1] will also be rejected. 716 func validateNoRepeatKeysInZone(zone *zonepb.ZoneConfig) error { 717 for _, constraints := range zone.Constraints { 718 // Because we expect to have a small number of constraints, a nested 719 // loop is probably better than allocating a map. 720 for i, curr := range constraints.Constraints { 721 for _, other := range constraints.Constraints[i+1:] { 722 // We don't want to enter the other validation logic if both of the constraints 723 // are attributes, due to the keys being the same for attributes. 724 if curr.Key == "" && other.Key == "" { 725 if curr.Value == other.Value { 726 return pgerror.Newf(pgcode.CheckViolation, 727 "incompatible zone constraints: %q and %q", curr, other) 728 } 729 } else { 730 if curr.Type == zonepb.Constraint_REQUIRED { 731 if other.Type == zonepb.Constraint_REQUIRED && other.Key == curr.Key || 732 other.Type == zonepb.Constraint_PROHIBITED && other.Key == curr.Key && other.Value == curr.Value { 733 return pgerror.Newf(pgcode.CheckViolation, 734 "incompatible zone constraints: %q and %q", curr, other) 735 } 736 } else if curr.Type == zonepb.Constraint_PROHIBITED { 737 // If we have a -k=v pair, verify that there are not any 738 // +k=v pairs in the constraints. 739 if other.Type == zonepb.Constraint_REQUIRED && other.Key == curr.Key && other.Value == curr.Value { 740 return pgerror.Newf(pgcode.CheckViolation, 741 "incompatible zone constraints: %q and %q", curr, other) 742 } 743 } 744 } 745 } 746 } 747 } 748 return nil 749 } 750 751 // validateZoneAttrsAndLocalities ensures that all constraints/lease preferences 752 // specified in the new zone config snippet are actually valid, meaning that 753 // they match at least one node. This protects against user typos causing 754 // zone configs that silently don't work as intended. 755 // 756 // Note that this really only catches typos in required constraints -- we don't 757 // want to reject prohibited constraints whose attributes/localities don't 758 // match any of the current nodes because it's a reasonable use case to add 759 // prohibited constraints for a new set of nodes before adding the new nodes to 760 // the cluster. If you had to first add one of the nodes before creating the 761 // constraints, data could be replicated there that shouldn't be. 762 func validateZoneAttrsAndLocalities( 763 ctx context.Context, getNodes nodeGetter, zone *zonepb.ZoneConfig, 764 ) error { 765 if len(zone.Constraints) == 0 && len(zone.LeasePreferences) == 0 { 766 return nil 767 } 768 769 // Given that we have something to validate, do the work to retrieve the 770 // set of attributes and localities present on at least one node. 771 nodes, err := getNodes(ctx, &serverpb.NodesRequest{}) 772 if err != nil { 773 return err 774 } 775 776 // Accumulate a unique list of constraints to validate. 777 toValidate := make([]zonepb.Constraint, 0) 778 addToValidate := func(c zonepb.Constraint) { 779 var alreadyInList bool 780 for _, val := range toValidate { 781 if c == val { 782 alreadyInList = true 783 break 784 } 785 } 786 if !alreadyInList { 787 toValidate = append(toValidate, c) 788 } 789 } 790 for _, constraints := range zone.Constraints { 791 for _, constraint := range constraints.Constraints { 792 addToValidate(constraint) 793 } 794 } 795 for _, leasePreferences := range zone.LeasePreferences { 796 for _, constraint := range leasePreferences.Constraints { 797 addToValidate(constraint) 798 } 799 } 800 801 // Check that each constraint matches some store somewhere in the cluster. 802 for _, constraint := range toValidate { 803 // We skip validation for negative constraints. See the function-level comment. 804 if constraint.Type == zonepb.Constraint_PROHIBITED { 805 continue 806 } 807 var found bool 808 node: 809 for _, node := range nodes.Nodes { 810 for _, store := range node.StoreStatuses { 811 // We could alternatively use zonepb.StoreMatchesConstraint here to 812 // catch typos in prohibited constraints as well, but as noted in the 813 // function-level comment that could break very reasonable use cases for 814 // prohibited constraints. 815 if zonepb.StoreSatisfiesConstraint(store.Desc, constraint) { 816 found = true 817 break node 818 } 819 } 820 } 821 if !found { 822 return pgerror.Newf(pgcode.CheckViolation, 823 "constraint %q matches no existing nodes within the cluster - did you enter it correctly?", 824 constraint) 825 } 826 } 827 828 return nil 829 } 830 831 func writeZoneConfig( 832 ctx context.Context, 833 txn *kv.Txn, 834 targetID sqlbase.ID, 835 table *sqlbase.TableDescriptor, 836 zone *zonepb.ZoneConfig, 837 execCfg *ExecutorConfig, 838 hasNewSubzones bool, 839 ) (numAffected int, err error) { 840 if len(zone.Subzones) > 0 { 841 st := execCfg.Settings 842 zone.SubzoneSpans, err = GenerateSubzoneSpans( 843 st, execCfg.ClusterID(), execCfg.Codec, table, zone.Subzones, hasNewSubzones) 844 if err != nil { 845 return 0, err 846 } 847 } else { 848 // To keep the Subzone and SubzoneSpan arrays consistent 849 zone.SubzoneSpans = nil 850 } 851 852 if zone.IsSubzonePlaceholder() && len(zone.Subzones) == 0 { 853 return execCfg.InternalExecutor.Exec(ctx, "delete-zone", txn, 854 "DELETE FROM system.zones WHERE id = $1", targetID) 855 } 856 857 buf, err := protoutil.Marshal(zone) 858 if err != nil { 859 return 0, pgerror.Newf(pgcode.CheckViolation, 860 "could not marshal zone config: %v", err) 861 } 862 return execCfg.InternalExecutor.Exec(ctx, "update-zone", txn, 863 "UPSERT INTO system.zones (id, config) VALUES ($1, $2)", targetID, buf) 864 } 865 866 // getZoneConfigRaw looks up the zone config with the given ID. Unlike 867 // getZoneConfig, it does not attempt to ascend the zone config hierarchy. If no 868 // zone config exists for the given ID, it returns nil. 869 func getZoneConfigRaw(ctx context.Context, txn *kv.Txn, id sqlbase.ID) (*zonepb.ZoneConfig, error) { 870 kv, err := txn.Get(ctx, config.MakeZoneKey(uint32(id))) 871 if err != nil { 872 return nil, err 873 } 874 if kv.Value == nil { 875 return nil, nil 876 } 877 var zone zonepb.ZoneConfig 878 if err := kv.ValueProto(&zone); err != nil { 879 return nil, err 880 } 881 return &zone, nil 882 } 883 884 // RemoveIndexZoneConfigs removes the zone configurations for some 885 // indexs being dropped. It is a no-op if there is no zone 886 // configuration. 887 // 888 // It operates entirely on the current goroutine and is thus able to 889 // reuse an existing client.Txn safely. 890 func RemoveIndexZoneConfigs( 891 ctx context.Context, 892 txn *kv.Txn, 893 execCfg *ExecutorConfig, 894 tableID sqlbase.ID, 895 indexDescs []sqlbase.IndexDescriptor, 896 ) error { 897 tableDesc, err := sqlbase.GetTableDescFromID(ctx, txn, execCfg.Codec, tableID) 898 if err != nil { 899 return err 900 } 901 902 zone, err := getZoneConfigRaw(ctx, txn, tableID) 903 if err != nil { 904 return err 905 } else if zone == nil { 906 zone = zonepb.NewZoneConfig() 907 } 908 909 for _, indexDesc := range indexDescs { 910 zone.DeleteIndexSubzones(uint32(indexDesc.ID)) 911 } 912 913 // Ignore CCL required error to allow schema change to progress. 914 _, err = writeZoneConfig(ctx, txn, tableID, tableDesc, zone, execCfg, false /* hasNewSubzones */) 915 if err != nil && !sqlbase.IsCCLRequiredError(err) { 916 return err 917 } 918 return nil 919 }