github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/config/zonepb/zone.go (about)

     1  // Copyright 2015 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 zonepb
    12  
    13  import (
    14  	"bytes"
    15  	"fmt"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/base"
    20  	"github.com/cockroachdb/cockroach/pkg/keys"
    21  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/opt/cat"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
    25  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    26  	"github.com/cockroachdb/errors"
    27  	"github.com/gogo/protobuf/proto"
    28  )
    29  
    30  // Several ranges outside of the SQL keyspace are given special names so they
    31  // can be targeted by zone configs.
    32  const (
    33  	DefaultZoneName    = "default"
    34  	LivenessZoneName   = "liveness"
    35  	MetaZoneName       = "meta"
    36  	SystemZoneName     = "system"
    37  	TimeseriesZoneName = "timeseries"
    38  )
    39  
    40  // NamedZones maps named zones to their pseudo-table ID that can be used to
    41  // install an entry into the system.zones table.
    42  var NamedZones = map[string]uint32{
    43  	DefaultZoneName:    keys.RootNamespaceID,
    44  	LivenessZoneName:   keys.LivenessRangesID,
    45  	MetaZoneName:       keys.MetaRangesID,
    46  	SystemZoneName:     keys.SystemRangesID,
    47  	TimeseriesZoneName: keys.TimeseriesRangesID,
    48  }
    49  
    50  // NamedZonesByID is the inverse of NamedZones: it maps pseudo-table IDs to
    51  // their zone names.
    52  var NamedZonesByID = func() map[uint32]string {
    53  	out := map[uint32]string{}
    54  	for name, id := range NamedZones {
    55  		out[id] = name
    56  	}
    57  	return out
    58  }()
    59  
    60  // ZoneSpecifierFromID creates a tree.ZoneSpecifier for the zone with the
    61  // given ID.
    62  func ZoneSpecifierFromID(
    63  	id uint32, resolveID func(id uint32) (parentID uint32, name string, err error),
    64  ) (tree.ZoneSpecifier, error) {
    65  	if name, ok := NamedZonesByID[id]; ok {
    66  		return tree.ZoneSpecifier{NamedZone: tree.UnrestrictedName(name)}, nil
    67  	}
    68  	parentID, name, err := resolveID(id)
    69  	if err != nil {
    70  		return tree.ZoneSpecifier{}, err
    71  	}
    72  	if parentID == keys.RootNamespaceID {
    73  		return tree.ZoneSpecifier{Database: tree.Name(name)}, nil
    74  	}
    75  	_, db, err := resolveID(parentID)
    76  	if err != nil {
    77  		return tree.ZoneSpecifier{}, err
    78  	}
    79  	return tree.ZoneSpecifier{
    80  		TableOrIndex: tree.TableIndexName{
    81  			Table: tree.MakeTableName(tree.Name(db), tree.Name(name)),
    82  		},
    83  	}, nil
    84  }
    85  
    86  // ResolveZoneSpecifier converts a zone specifier to the ID of most specific
    87  // zone whose config applies.
    88  func ResolveZoneSpecifier(
    89  	zs *tree.ZoneSpecifier, resolveName func(parentID uint32, name string) (id uint32, err error),
    90  ) (uint32, error) {
    91  	// A zone specifier has one of 3 possible structures:
    92  	// - a predefined named zone;
    93  	// - a database name;
    94  	// - a table or index name.
    95  	if zs.NamedZone != "" {
    96  		if zs.NamedZone == DefaultZoneName {
    97  			return keys.RootNamespaceID, nil
    98  		}
    99  		if id, ok := NamedZones[string(zs.NamedZone)]; ok {
   100  			return id, nil
   101  		}
   102  		return 0, fmt.Errorf("%q is not a built-in zone", string(zs.NamedZone))
   103  	}
   104  
   105  	if zs.Database != "" {
   106  		return resolveName(keys.RootNamespaceID, string(zs.Database))
   107  	}
   108  
   109  	// Third case: a table or index name. We look up the table part here.
   110  
   111  	tn := &zs.TableOrIndex.Table
   112  	if tn.SchemaName != tree.PublicSchemaName {
   113  		return 0, pgerror.Newf(pgcode.ReservedName,
   114  			"only schema \"public\" is supported: %q", tree.ErrString(tn))
   115  	}
   116  	databaseID, err := resolveName(keys.RootNamespaceID, tn.Catalog())
   117  	if err != nil {
   118  		return 0, err
   119  	}
   120  	return resolveName(databaseID, tn.Table())
   121  }
   122  
   123  func (c Constraint) String() string {
   124  	var str string
   125  	switch c.Type {
   126  	case Constraint_REQUIRED:
   127  		str += "+"
   128  	case Constraint_PROHIBITED:
   129  		str += "-"
   130  	}
   131  	if len(c.Key) > 0 {
   132  		str += c.Key + "="
   133  	}
   134  	str += c.Value
   135  	return str
   136  }
   137  
   138  // FromString populates the constraint from the constraint shorthand notation.
   139  func (c *Constraint) FromString(short string) error {
   140  	if len(short) == 0 {
   141  		return fmt.Errorf("the empty string is not a valid constraint")
   142  	}
   143  	switch short[0] {
   144  	case '+':
   145  		c.Type = Constraint_REQUIRED
   146  		short = short[1:]
   147  	case '-':
   148  		c.Type = Constraint_PROHIBITED
   149  		short = short[1:]
   150  	default:
   151  		c.Type = Constraint_DEPRECATED_POSITIVE
   152  	}
   153  	parts := strings.Split(short, "=")
   154  	if len(parts) == 1 {
   155  		c.Value = parts[0]
   156  	} else if len(parts) == 2 {
   157  		c.Key = parts[0]
   158  		c.Value = parts[1]
   159  	} else {
   160  		return errors.Errorf("constraint needs to be in the form \"(key=)value\", not %q", short)
   161  	}
   162  	return nil
   163  }
   164  
   165  // NewZoneConfig is the zone configuration used when no custom
   166  // config has been specified.
   167  func NewZoneConfig() *ZoneConfig {
   168  	return &ZoneConfig{
   169  		InheritedConstraints:      true,
   170  		InheritedLeasePreferences: true,
   171  	}
   172  }
   173  
   174  // EmptyCompleteZoneConfig is the zone configuration where
   175  // all fields are set but set to their respective zero values.
   176  func EmptyCompleteZoneConfig() *ZoneConfig {
   177  	return &ZoneConfig{
   178  		NumReplicas:               proto.Int32(0),
   179  		RangeMinBytes:             proto.Int64(0),
   180  		RangeMaxBytes:             proto.Int64(0),
   181  		GC:                        &GCPolicy{TTLSeconds: 0},
   182  		InheritedConstraints:      true,
   183  		InheritedLeasePreferences: true,
   184  	}
   185  }
   186  
   187  // DefaultZoneConfig is the default zone configuration used when no custom
   188  // config has been specified.
   189  func DefaultZoneConfig() ZoneConfig {
   190  	return ZoneConfig{
   191  		NumReplicas:   proto.Int32(3),
   192  		RangeMinBytes: proto.Int64(128 << 20), // 128 MB
   193  		RangeMaxBytes: proto.Int64(512 << 20), // 512 MB
   194  		GC: &GCPolicy{
   195  			// Use 25 hours instead of the previous 24 to make users successful by
   196  			// default. Users desiring to take incremental backups every 24h may
   197  			// incorrectly assume that the previous default 24h was sufficient to do
   198  			// that. But the equation for incremental backups is:
   199  			// 	GC TTLSeconds >= (desired backup interval) + (time to perform incremental backup)
   200  			// We think most new users' incremental backups will complete within an
   201  			// hour, and larger clusters will have more experienced operators and will
   202  			// understand how to change these settings if needed.
   203  			TTLSeconds: 25 * 60 * 60,
   204  		},
   205  	}
   206  }
   207  
   208  // DefaultZoneConfigRef is the default zone configuration used when no custom
   209  // config has been specified.
   210  func DefaultZoneConfigRef() *ZoneConfig {
   211  	zoneConfig := DefaultZoneConfig()
   212  	return &zoneConfig
   213  }
   214  
   215  // DefaultSystemZoneConfig is the default zone configuration used when no custom
   216  // config has been specified. The DefaultSystemZoneConfig is like the
   217  // DefaultZoneConfig but has a replication factor of 5 instead of 3.
   218  func DefaultSystemZoneConfig() ZoneConfig {
   219  	defaultSystemZoneConfig := DefaultZoneConfig()
   220  	defaultSystemZoneConfig.NumReplicas = proto.Int32(5)
   221  	return defaultSystemZoneConfig
   222  }
   223  
   224  // DefaultSystemZoneConfigRef is the default zone configuration used when no custom
   225  // config has been specified.
   226  func DefaultSystemZoneConfigRef() *ZoneConfig {
   227  	systemZoneConfig := DefaultSystemZoneConfig()
   228  	return &systemZoneConfig
   229  }
   230  
   231  // IsComplete returns whether all the fields are set.
   232  func (z *ZoneConfig) IsComplete() bool {
   233  	return ((z.NumReplicas != nil) && (z.RangeMinBytes != nil) &&
   234  		(z.RangeMaxBytes != nil) && (z.GC != nil) &&
   235  		(!z.InheritedConstraints) && (!z.InheritedLeasePreferences))
   236  }
   237  
   238  // ValidateTandemFields returns an error if the ZoneConfig to be written
   239  // specifies a configuration that could cause problems with the introduction
   240  // of cascading zone configs.
   241  func (z *ZoneConfig) ValidateTandemFields() error {
   242  	var numConstrainedRepls int32
   243  	for _, constraint := range z.Constraints {
   244  		numConstrainedRepls += constraint.NumReplicas
   245  	}
   246  
   247  	if numConstrainedRepls > 0 && z.NumReplicas == nil {
   248  		return fmt.Errorf("when per-replica constraints are set, num_replicas must be set as well")
   249  	}
   250  	if (z.RangeMinBytes != nil || z.RangeMaxBytes != nil) &&
   251  		(z.RangeMinBytes == nil || z.RangeMaxBytes == nil) {
   252  		return fmt.Errorf("range_min_bytes and range_max_bytes must be set together")
   253  	}
   254  	if !z.InheritedLeasePreferences && z.InheritedConstraints {
   255  		return fmt.Errorf("lease preferences can not be set unless the constraints are explicitly set as well")
   256  	}
   257  	return nil
   258  }
   259  
   260  // Validate returns an error if the ZoneConfig specifies a known-dangerous or
   261  // disallowed configuration.
   262  func (z *ZoneConfig) Validate() error {
   263  	for _, s := range z.Subzones {
   264  		if err := s.Config.Validate(); err != nil {
   265  			return err
   266  		}
   267  	}
   268  
   269  	if z.NumReplicas != nil {
   270  		switch {
   271  		case *z.NumReplicas < 0:
   272  			return fmt.Errorf("at least one replica is required")
   273  		case *z.NumReplicas == 0:
   274  			if len(z.Subzones) > 0 {
   275  				// NumReplicas == 0 is allowed when this ZoneConfig is a subzone
   276  				// placeholder. See IsSubzonePlaceholder.
   277  				return nil
   278  			}
   279  			return fmt.Errorf("at least one replica is required")
   280  		case *z.NumReplicas == 2:
   281  			return fmt.Errorf("at least 3 replicas are required for multi-replica configurations")
   282  		}
   283  	}
   284  
   285  	if z.RangeMaxBytes != nil && *z.RangeMaxBytes < base.MinRangeMaxBytes {
   286  		return fmt.Errorf("RangeMaxBytes %d less than minimum allowed %d",
   287  			*z.RangeMaxBytes, base.MinRangeMaxBytes)
   288  	}
   289  
   290  	if z.RangeMinBytes != nil && *z.RangeMinBytes < 0 {
   291  		return fmt.Errorf("RangeMinBytes %d less than minimum allowed 0", *z.RangeMinBytes)
   292  	}
   293  	if z.RangeMinBytes != nil && z.RangeMaxBytes != nil && *z.RangeMinBytes >= *z.RangeMaxBytes {
   294  		return fmt.Errorf("RangeMinBytes %d is greater than or equal to RangeMaxBytes %d",
   295  			*z.RangeMinBytes, *z.RangeMaxBytes)
   296  	}
   297  
   298  	// Reserve the value 0 to potentially have some special meaning in the future,
   299  	// such as to disable GC.
   300  	if z.GC != nil && z.GC.TTLSeconds < 1 {
   301  		return fmt.Errorf("GC.TTLSeconds %d less than minimum allowed 1", z.GC.TTLSeconds)
   302  	}
   303  
   304  	for _, constraints := range z.Constraints {
   305  		for _, constraint := range constraints.Constraints {
   306  			if constraint.Type == Constraint_DEPRECATED_POSITIVE {
   307  				return fmt.Errorf("constraints must either be required (prefixed with a '+') or " +
   308  					"prohibited (prefixed with a '-')")
   309  			}
   310  		}
   311  	}
   312  
   313  	// We only need to further validate constraints if per-replica constraints
   314  	// are in use. The old style of constraints that apply to all replicas don't
   315  	// require validation.
   316  	if len(z.Constraints) > 1 || (len(z.Constraints) == 1 && z.Constraints[0].NumReplicas != 0) {
   317  		var numConstrainedRepls int64
   318  		for _, constraints := range z.Constraints {
   319  			if constraints.NumReplicas <= 0 {
   320  				return fmt.Errorf("constraints must apply to at least one replica")
   321  			}
   322  			numConstrainedRepls += int64(constraints.NumReplicas)
   323  			for _, constraint := range constraints.Constraints {
   324  				// TODO(a-robinson): Relax this constraint to allow prohibited replicas,
   325  				// as discussed on #23014.
   326  				if constraint.Type != Constraint_REQUIRED && z.NumReplicas != nil && constraints.NumReplicas != *z.NumReplicas {
   327  					return fmt.Errorf(
   328  						"only required constraints (prefixed with a '+') can be applied to a subset of replicas")
   329  				}
   330  			}
   331  		}
   332  		if z.NumReplicas != nil && numConstrainedRepls > int64(*z.NumReplicas) {
   333  			return fmt.Errorf("the number of replicas specified in constraints (%d) cannot be greater "+
   334  				"than the number of replicas configured for the zone (%d)",
   335  				numConstrainedRepls, *z.NumReplicas)
   336  		}
   337  	}
   338  
   339  	for _, leasePref := range z.LeasePreferences {
   340  		if len(leasePref.Constraints) == 0 {
   341  			return fmt.Errorf("every lease preference must include at least one constraint")
   342  		}
   343  		for _, constraint := range leasePref.Constraints {
   344  			if constraint.Type == Constraint_DEPRECATED_POSITIVE {
   345  				return fmt.Errorf("lease preference constraints must either be required " +
   346  					"(prefixed with a '+') or prohibited (prefixed with a '-')")
   347  			}
   348  		}
   349  	}
   350  
   351  	return nil
   352  }
   353  
   354  // InheritFromParent hydrates a zones missing fields from its parent.
   355  func (z *ZoneConfig) InheritFromParent(parent *ZoneConfig) {
   356  	// Allow for subzonePlaceholders to inherit fields from parents if needed.
   357  	if z.NumReplicas == nil || (z.NumReplicas != nil && *z.NumReplicas == 0) {
   358  		if parent.NumReplicas != nil {
   359  			z.NumReplicas = proto.Int32(*parent.NumReplicas)
   360  		}
   361  	}
   362  	if z.RangeMinBytes == nil {
   363  		if parent.RangeMinBytes != nil {
   364  			z.RangeMinBytes = proto.Int64(*parent.RangeMinBytes)
   365  		}
   366  	}
   367  	if z.RangeMaxBytes == nil {
   368  		if parent.RangeMaxBytes != nil {
   369  			z.RangeMaxBytes = proto.Int64(*parent.RangeMaxBytes)
   370  		}
   371  	}
   372  	if z.GC == nil {
   373  		if parent.GC != nil {
   374  			tempGC := *parent.GC
   375  			z.GC = &tempGC
   376  		}
   377  	}
   378  	if z.InheritedConstraints {
   379  		if !parent.InheritedConstraints {
   380  			z.Constraints = parent.Constraints
   381  			z.InheritedConstraints = false
   382  		}
   383  	}
   384  	if z.InheritedLeasePreferences {
   385  		if !parent.InheritedLeasePreferences {
   386  			z.LeasePreferences = parent.LeasePreferences
   387  			z.InheritedLeasePreferences = false
   388  		}
   389  	}
   390  }
   391  
   392  // CopyFromZone copies over the specified fields from the other zone.
   393  func (z *ZoneConfig) CopyFromZone(other ZoneConfig, fieldList []tree.Name) {
   394  	for _, fieldName := range fieldList {
   395  		if fieldName == "num_replicas" {
   396  			z.NumReplicas = nil
   397  			if other.NumReplicas != nil {
   398  				z.NumReplicas = proto.Int32(*other.NumReplicas)
   399  			}
   400  		}
   401  		if fieldName == "range_min_bytes" {
   402  			z.RangeMinBytes = nil
   403  			if other.RangeMinBytes != nil {
   404  				z.RangeMinBytes = proto.Int64(*other.RangeMinBytes)
   405  			}
   406  		}
   407  		if fieldName == "range_max_bytes" {
   408  			z.RangeMaxBytes = nil
   409  			if other.RangeMaxBytes != nil {
   410  				z.RangeMaxBytes = proto.Int64(*other.RangeMaxBytes)
   411  			}
   412  		}
   413  		if fieldName == "gc.ttlseconds" {
   414  			z.GC = nil
   415  			if other.GC != nil {
   416  				tempGC := *other.GC
   417  				z.GC = &tempGC
   418  			}
   419  		}
   420  		if fieldName == "constraints" {
   421  			z.Constraints = other.Constraints
   422  			z.InheritedConstraints = other.InheritedConstraints
   423  		}
   424  		if fieldName == "lease_preferences" {
   425  			z.LeasePreferences = other.LeasePreferences
   426  			z.InheritedLeasePreferences = other.InheritedLeasePreferences
   427  		}
   428  	}
   429  }
   430  
   431  // StoreSatisfiesConstraint checks whether a store satisfies the given constraint.
   432  // If the constraint is of the PROHIBITED type, satisfying it means the store
   433  // not matching the constraint's spec.
   434  func StoreSatisfiesConstraint(store roachpb.StoreDescriptor, constraint Constraint) bool {
   435  	hasConstraint := StoreMatchesConstraint(store, constraint)
   436  	if (constraint.Type == Constraint_REQUIRED && !hasConstraint) ||
   437  		(constraint.Type == Constraint_PROHIBITED && hasConstraint) {
   438  		return false
   439  	}
   440  	return true
   441  }
   442  
   443  // StoreMatchesConstraint returns whether a store's attributes or node's
   444  // locality match the constraint's spec. It notably ignores whether the
   445  // constraint is required, prohibited, positive, or otherwise.
   446  // Also see StoreSatisfiesConstraint().
   447  func StoreMatchesConstraint(store roachpb.StoreDescriptor, c Constraint) bool {
   448  	if c.Key == "" {
   449  		for _, attrs := range []roachpb.Attributes{store.Attrs, store.Node.Attrs} {
   450  			for _, attr := range attrs.Attrs {
   451  				if attr == c.Value {
   452  					return true
   453  				}
   454  			}
   455  		}
   456  		return false
   457  	}
   458  	for _, tier := range store.Node.Locality.Tiers {
   459  		if c.Key == tier.Key && c.Value == tier.Value {
   460  			return true
   461  		}
   462  	}
   463  	return false
   464  }
   465  
   466  // DeleteTableConfig removes any configuration that applies to the table
   467  // targeted by this ZoneConfig, leaving only its subzone configs, if any. After
   468  // calling DeleteTableConfig, IsSubzonePlaceholder will return true.
   469  //
   470  // Only table zones can have subzones, so it does not make sense to call this
   471  // method on non-table ZoneConfigs.
   472  func (z *ZoneConfig) DeleteTableConfig() {
   473  	*z = ZoneConfig{
   474  		// Have to set NumReplicas to 0 so it is recognized as a placeholder.
   475  		NumReplicas:  proto.Int32(0),
   476  		Subzones:     z.Subzones,
   477  		SubzoneSpans: z.SubzoneSpans,
   478  	}
   479  }
   480  
   481  // IsSubzonePlaceholder returns whether the ZoneConfig exists only to store
   482  // subzones. The configuration fields (e.g., RangeMinBytes) in a subzone
   483  // placeholder should be ignored; instead, the configuration from the parent
   484  // ZoneConfig applies.
   485  func (z *ZoneConfig) IsSubzonePlaceholder() bool {
   486  	// A ZoneConfig with zero replicas is otherwise invalid, so we repurpose it to
   487  	// indicate that a ZoneConfig is a placeholder for subzones rather than
   488  	// introducing a dedicated IsPlaceholder flag.
   489  	return z.NumReplicas != nil && *z.NumReplicas == 0
   490  }
   491  
   492  // GetSubzone returns the most specific Subzone that applies to the specified
   493  // index ID and partition, if any exists. The partition can be left unspecified
   494  // to get the Subzone for an entire index, if it exists. indexID, however, must
   495  // always be provided, even when looking for a partition's Subzone.
   496  func (z *ZoneConfig) GetSubzone(indexID uint32, partition string) *Subzone {
   497  	for _, s := range z.Subzones {
   498  		if s.IndexID == indexID && s.PartitionName == partition {
   499  			copySubzone := s
   500  			return &copySubzone
   501  		}
   502  	}
   503  	if partition != "" {
   504  		return z.GetSubzone(indexID, "")
   505  	}
   506  	return nil
   507  }
   508  
   509  // GetSubzoneExact is similar to GetSubzone but does not find the most specific
   510  // subzone that applies to a specified index and partition, as it finds either the
   511  // exact config that applies, or returns nil.
   512  func (z *ZoneConfig) GetSubzoneExact(indexID uint32, partition string) *Subzone {
   513  	for _, s := range z.Subzones {
   514  		if s.IndexID == indexID && s.PartitionName == partition {
   515  			copySubzone := s
   516  			return &copySubzone
   517  		}
   518  	}
   519  	return nil
   520  }
   521  
   522  // GetSubzoneForKeySuffix returns the ZoneConfig for the subzone that contains
   523  // keySuffix, if it exists and its position in the subzones slice.
   524  func (z ZoneConfig) GetSubzoneForKeySuffix(keySuffix []byte) (*Subzone, int32) {
   525  	// TODO(benesch): Use binary search instead.
   526  	for _, s := range z.SubzoneSpans {
   527  		// The span's Key is stored with the prefix removed, so we can compare
   528  		// directly to keySuffix. An unset EndKey implies Key.PrefixEnd().
   529  		if (s.Key.Compare(keySuffix) <= 0) &&
   530  			((s.EndKey == nil && bytes.HasPrefix(keySuffix, s.Key)) || s.EndKey.Compare(keySuffix) > 0) {
   531  			copySubzone := z.Subzones[s.SubzoneIndex]
   532  			return &copySubzone, s.SubzoneIndex
   533  		}
   534  	}
   535  	return nil, -1
   536  }
   537  
   538  // SetSubzone installs subzone into the ZoneConfig, overwriting any existing
   539  // subzone with the same IndexID and PartitionName.
   540  func (z *ZoneConfig) SetSubzone(subzone Subzone) {
   541  	for i, s := range z.Subzones {
   542  		if s.IndexID == subzone.IndexID && s.PartitionName == subzone.PartitionName {
   543  			z.Subzones[i] = subzone
   544  			return
   545  		}
   546  	}
   547  	z.Subzones = append(z.Subzones, subzone)
   548  }
   549  
   550  // DeleteSubzone removes the subzone with the specified index ID and partition.
   551  // It returns whether it performed any work.
   552  func (z *ZoneConfig) DeleteSubzone(indexID uint32, partition string) bool {
   553  	for i, s := range z.Subzones {
   554  		if s.IndexID == indexID && s.PartitionName == partition {
   555  			z.Subzones = append(z.Subzones[:i], z.Subzones[i+1:]...)
   556  			return true
   557  		}
   558  	}
   559  	return false
   560  }
   561  
   562  // DeleteIndexSubzones deletes all subzones that refer to the index with the
   563  // specified ID. This includes subzones for partitions of the index as well as
   564  // the index subzone itself.
   565  func (z *ZoneConfig) DeleteIndexSubzones(indexID uint32) {
   566  	subzones := z.Subzones[:0]
   567  	for _, s := range z.Subzones {
   568  		if s.IndexID != indexID {
   569  			subzones = append(subzones, s)
   570  		}
   571  	}
   572  	z.Subzones = subzones
   573  }
   574  
   575  // SubzoneSplits returns the split points determined by a ZoneConfig's subzones.
   576  func (z ZoneConfig) SubzoneSplits() []roachpb.RKey {
   577  	var out []roachpb.RKey
   578  	for _, span := range z.SubzoneSpans {
   579  		// TODO(benesch): avoid a split at the first partition's start key when it
   580  		// is the minimum possible value.
   581  		if len(out) == 0 || !out[len(out)-1].Equal(span.Key) {
   582  			// Only split at the start key when it differs from the last end key.
   583  			out = append(out, roachpb.RKey(span.Key))
   584  		}
   585  		endKey := span.EndKey
   586  		if len(endKey) == 0 {
   587  			endKey = span.Key.PrefixEnd()
   588  		}
   589  		out = append(out, roachpb.RKey(endKey))
   590  		// TODO(benesch): avoid a split at the last partition's end key when it is
   591  		// the maximum possible value.
   592  	}
   593  	return out
   594  }
   595  
   596  // ReplicaConstraintsCount is part of the cat.Zone interface.
   597  func (z *ZoneConfig) ReplicaConstraintsCount() int {
   598  	return len(z.Constraints)
   599  }
   600  
   601  // ReplicaConstraints is part of the cat.Zone interface.
   602  func (z *ZoneConfig) ReplicaConstraints(i int) cat.ReplicaConstraints {
   603  	return &z.Constraints[i]
   604  }
   605  
   606  // LeasePreferenceCount is part of the cat.Zone interface.
   607  func (z *ZoneConfig) LeasePreferenceCount() int {
   608  	return len(z.LeasePreferences)
   609  }
   610  
   611  // LeasePreference is part of the cat.Zone interface.
   612  func (z *ZoneConfig) LeasePreference(i int) cat.ConstraintSet {
   613  	return &z.LeasePreferences[i]
   614  }
   615  
   616  // ConstraintCount is part of the cat.LeasePreference interface.
   617  func (l *LeasePreference) ConstraintCount() int {
   618  	return len(l.Constraints)
   619  }
   620  
   621  // Constraint is part of the cat.LeasePreference interface.
   622  func (l *LeasePreference) Constraint(i int) cat.Constraint {
   623  	return &l.Constraints[i]
   624  }
   625  
   626  func (c ConstraintsConjunction) String() string {
   627  	var sb strings.Builder
   628  	for i, cons := range c.Constraints {
   629  		if i > 0 {
   630  			sb.WriteRune(',')
   631  		}
   632  		sb.WriteString(cons.String())
   633  	}
   634  	if c.NumReplicas != 0 {
   635  		fmt.Fprintf(&sb, ":%d", c.NumReplicas)
   636  	}
   637  	return sb.String()
   638  }
   639  
   640  // ReplicaCount is part of the cat.ReplicaConstraints interface.
   641  func (c *ConstraintsConjunction) ReplicaCount() int32 {
   642  	return c.NumReplicas
   643  }
   644  
   645  // ConstraintCount is part of the cat.ReplicaConstraints interface.
   646  func (c *ConstraintsConjunction) ConstraintCount() int {
   647  	return len(c.Constraints)
   648  }
   649  
   650  // Constraint is part of the cat.ReplicaConstraints interface.
   651  func (c *ConstraintsConjunction) Constraint(i int) cat.Constraint {
   652  	return &c.Constraints[i]
   653  }
   654  
   655  // IsRequired is part of the cat.Constraint interface.
   656  func (c *Constraint) IsRequired() bool {
   657  	return c.Type == Constraint_REQUIRED
   658  }
   659  
   660  // GetKey is part of the cat.Constraint interface.
   661  func (c *Constraint) GetKey() string {
   662  	return c.Key
   663  }
   664  
   665  // GetValue is part of the cat.Constraint interface.
   666  func (c *Constraint) GetValue() string {
   667  	return c.Value
   668  }
   669  
   670  // TTL returns the implies TTL as a time.Duration.
   671  func (m *GCPolicy) TTL() time.Duration {
   672  	return time.Duration(m.TTLSeconds) * time.Second
   673  }