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  }