github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/show_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  	"bytes"
    15  	"context"
    16  	"strings"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/config/zonepb"
    19  	"github.com/cockroachdb/cockroach/pkg/keys"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/lex"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    24  	"github.com/cockroachdb/cockroach/pkg/util/protoutil"
    25  	"github.com/cockroachdb/errors"
    26  	"gopkg.in/yaml.v2"
    27  )
    28  
    29  // These must match crdb_internal.zones.
    30  var showZoneConfigColumns = sqlbase.ResultColumns{
    31  	{Name: "zone_id", Typ: types.Int, Hidden: true},
    32  	{Name: "subzone_id", Typ: types.Int, Hidden: true},
    33  	{Name: "target", Typ: types.String},
    34  	{Name: "range_name", Typ: types.String, Hidden: true},
    35  	{Name: "database_name", Typ: types.String, Hidden: true},
    36  	{Name: "table_name", Typ: types.String, Hidden: true},
    37  	{Name: "index_name", Typ: types.String, Hidden: true},
    38  	{Name: "partition_name", Typ: types.String, Hidden: true},
    39  	{Name: "raw_config_yaml", Typ: types.String, Hidden: true},
    40  	{Name: "raw_config_sql", Typ: types.String},
    41  	{Name: "raw_config_protobuf", Typ: types.Bytes, Hidden: true},
    42  	{Name: "full_config_yaml", Typ: types.String, Hidden: true},
    43  	{Name: "full_config_sql", Typ: types.String, Hidden: true},
    44  }
    45  
    46  // These must match showZoneConfigColumns.
    47  const (
    48  	zoneIDCol int = iota
    49  	subZoneIDCol
    50  	targetCol
    51  	rangeNameCol
    52  	databaseNameCol
    53  	tableNameCol
    54  	indexNameCol
    55  	partitionNameCol
    56  	rawConfigYAMLCol
    57  	rawConfigSQLCol
    58  	rawConfigProtobufCol
    59  	fullConfigYamlCol
    60  	fullConfigSQLCol
    61  )
    62  
    63  func (p *planner) ShowZoneConfig(ctx context.Context, n *tree.ShowZoneConfig) (planNode, error) {
    64  	return &delayedNode{
    65  		name:    n.String(),
    66  		columns: showZoneConfigColumns,
    67  		constructor: func(ctx context.Context, p *planner) (planNode, error) {
    68  			v := p.newContainerValuesNode(showZoneConfigColumns, 0)
    69  
    70  			// This signifies SHOW ALL.
    71  			// However, SHOW ALL should be handled by the delegate.
    72  			if n.ZoneSpecifier == (tree.ZoneSpecifier{}) {
    73  				return nil, errors.AssertionFailedf("zone must be specified")
    74  			}
    75  
    76  			row, err := getShowZoneConfigRow(ctx, p, n.ZoneSpecifier)
    77  			if err != nil {
    78  				v.Close(ctx)
    79  				return nil, err
    80  			}
    81  			if _, err := v.rows.AddRow(ctx, row); err != nil {
    82  				v.Close(ctx)
    83  				return nil, err
    84  			}
    85  			return v, nil
    86  		},
    87  	}, nil
    88  }
    89  
    90  func getShowZoneConfigRow(
    91  	ctx context.Context, p *planner, zoneSpecifier tree.ZoneSpecifier,
    92  ) (tree.Datums, error) {
    93  	tblDesc, err := p.resolveTableForZone(ctx, &zoneSpecifier)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	if zoneSpecifier.TableOrIndex.Table.ObjectName != "" {
    99  		if err = p.CheckAnyPrivilege(ctx, tblDesc); err != nil {
   100  			return nil, err
   101  		}
   102  	} else if zoneSpecifier.Database != "" {
   103  		database, err := p.ResolveUncachedDatabaseByName(
   104  			ctx,
   105  			string(zoneSpecifier.Database),
   106  			true, /* required */
   107  		)
   108  		if err != nil {
   109  			return nil, err
   110  		}
   111  		if err = p.CheckAnyPrivilege(ctx, database); err != nil {
   112  			return nil, err
   113  		}
   114  	}
   115  
   116  	targetID, err := resolveZone(ctx, p.txn, &zoneSpecifier)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	index, partition, err := resolveSubzone(&zoneSpecifier, tblDesc)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	subZoneIdx := uint32(0)
   127  	zoneID, zone, subzone, err := GetZoneConfigInTxn(ctx, p.txn,
   128  		uint32(targetID), index, partition, false /* getInheritedDefault */)
   129  	if errors.Is(err, errNoZoneConfigApplies) {
   130  		// TODO(benesch): This shouldn't be the caller's responsibility;
   131  		// GetZoneConfigInTxn should just return the default zone config if no zone
   132  		// config applies.
   133  		zone = p.execCfg.DefaultZoneConfig
   134  		zoneID = keys.RootNamespaceID
   135  	} else if err != nil {
   136  		return nil, err
   137  	} else if subzone != nil {
   138  		for i := range zone.Subzones {
   139  			subZoneIdx++
   140  			if subzone == &zone.Subzones[i] {
   141  				break
   142  			}
   143  		}
   144  		zone = &subzone.Config
   145  	}
   146  
   147  	// Determine the zone specifier for the zone config that actually applies
   148  	// without performing another KV lookup.
   149  	zs := ascendZoneSpecifier(zoneSpecifier, uint32(targetID), zoneID, subzone)
   150  
   151  	// Ensure subzone configs don't infect the output of config_bytes.
   152  	zone.Subzones = nil
   153  	zone.SubzoneSpans = nil
   154  
   155  	vals := make(tree.Datums, len(showZoneConfigColumns))
   156  	if err := generateZoneConfigIntrospectionValues(
   157  		vals, tree.NewDInt(tree.DInt(zoneID)), tree.NewDInt(tree.DInt(subZoneIdx)), &zs, zone, nil,
   158  	); err != nil {
   159  		return nil, err
   160  	}
   161  	return vals, nil
   162  }
   163  
   164  // zoneConfigToSQL pretty prints a zone configuration as a SQL string.
   165  func zoneConfigToSQL(zs *tree.ZoneSpecifier, zone *zonepb.ZoneConfig) (string, error) {
   166  	constraints, err := yamlMarshalFlow(zonepb.ConstraintsList{
   167  		Constraints: zone.Constraints,
   168  		Inherited:   zone.InheritedConstraints})
   169  	if err != nil {
   170  		return "", err
   171  	}
   172  	constraints = strings.TrimSpace(constraints)
   173  	prefs, err := yamlMarshalFlow(zone.LeasePreferences)
   174  	if err != nil {
   175  		return "", err
   176  	}
   177  	prefs = strings.TrimSpace(prefs)
   178  
   179  	useComma := false
   180  	f := tree.NewFmtCtx(tree.FmtParsable)
   181  	f.WriteString("ALTER ")
   182  	f.FormatNode(zs)
   183  	f.WriteString(" CONFIGURE ZONE USING\n")
   184  	if zone.RangeMinBytes != nil {
   185  		f.Printf("\trange_min_bytes = %d", *zone.RangeMinBytes)
   186  		useComma = true
   187  	}
   188  	if zone.RangeMaxBytes != nil {
   189  		writeComma(f, useComma)
   190  		f.Printf("\trange_max_bytes = %d", *zone.RangeMaxBytes)
   191  		useComma = true
   192  	}
   193  	if zone.GC != nil {
   194  		writeComma(f, useComma)
   195  		f.Printf("\tgc.ttlseconds = %d", zone.GC.TTLSeconds)
   196  		useComma = true
   197  	}
   198  	if zone.NumReplicas != nil {
   199  		writeComma(f, useComma)
   200  		f.Printf("\tnum_replicas = %d", *zone.NumReplicas)
   201  		useComma = true
   202  	}
   203  	if !zone.InheritedConstraints {
   204  		writeComma(f, useComma)
   205  		f.Printf("\tconstraints = %s", lex.EscapeSQLString(constraints))
   206  		useComma = true
   207  	}
   208  	if !zone.InheritedLeasePreferences {
   209  		writeComma(f, useComma)
   210  		f.Printf("\tlease_preferences = %s", lex.EscapeSQLString(prefs))
   211  	}
   212  	return f.String(), nil
   213  }
   214  
   215  // generateZoneConfigIntrospectionValues creates a result row
   216  // suitable for populating crdb_internal.zones or SHOW ZONE CONFIG.
   217  // The values are populated into the `values` first argument.
   218  // The caller is responsible for creating the DInt for the ID and
   219  // provide it as 2nd argument. The function will compute
   220  // the remaining values based on the zone specifier and configuration.
   221  // The fullZoneConfig argument is a zone config populated with all
   222  // inherited zone configuration information. If this argument is nil,
   223  // then the zone argument is used to populate the full_config_sql and
   224  // full_config_yaml columns.
   225  func generateZoneConfigIntrospectionValues(
   226  	values tree.Datums,
   227  	zoneID tree.Datum,
   228  	subZoneID tree.Datum,
   229  	zs *tree.ZoneSpecifier,
   230  	zone *zonepb.ZoneConfig,
   231  	fullZoneConfig *zonepb.ZoneConfig,
   232  ) error {
   233  	// Populate the ID column.
   234  	values[zoneIDCol] = zoneID
   235  	values[subZoneIDCol] = subZoneID
   236  
   237  	// Populate the zone specifier columns.
   238  	values[targetCol] = tree.DNull
   239  	values[rangeNameCol] = tree.DNull
   240  	values[databaseNameCol] = tree.DNull
   241  	values[tableNameCol] = tree.DNull
   242  	values[indexNameCol] = tree.DNull
   243  	values[partitionNameCol] = tree.DNull
   244  	if zs != nil {
   245  		values[targetCol] = tree.NewDString(zs.String())
   246  		if zs.NamedZone != "" {
   247  			values[rangeNameCol] = tree.NewDString(string(zs.NamedZone))
   248  		}
   249  		if zs.Database != "" {
   250  			values[databaseNameCol] = tree.NewDString(string(zs.Database))
   251  		}
   252  		if zs.TableOrIndex.Table.ObjectName != "" {
   253  			values[databaseNameCol] = tree.NewDString(string(zs.TableOrIndex.Table.CatalogName))
   254  			values[tableNameCol] = tree.NewDString(string(zs.TableOrIndex.Table.ObjectName))
   255  		}
   256  		if zs.TableOrIndex.Index != "" {
   257  			values[indexNameCol] = tree.NewDString(string(zs.TableOrIndex.Index))
   258  		}
   259  		if zs.Partition != "" {
   260  			values[partitionNameCol] = tree.NewDString(string(zs.Partition))
   261  		}
   262  	}
   263  
   264  	// Populate the YAML column.
   265  	yamlConfig, err := yaml.Marshal(zone)
   266  	if err != nil {
   267  		return err
   268  	}
   269  	values[rawConfigYAMLCol] = tree.NewDString(string(yamlConfig))
   270  
   271  	// Populate the SQL column.
   272  	if zs == nil {
   273  		values[rawConfigSQLCol] = tree.DNull
   274  	} else {
   275  		sqlStr, err := zoneConfigToSQL(zs, zone)
   276  		if err != nil {
   277  			return err
   278  		}
   279  		values[rawConfigSQLCol] = tree.NewDString(sqlStr)
   280  	}
   281  
   282  	// Populate the protobuf column.
   283  	protoConfig, err := protoutil.Marshal(zone)
   284  	if err != nil {
   285  		return err
   286  	}
   287  	values[rawConfigProtobufCol] = tree.NewDBytes(tree.DBytes(protoConfig))
   288  
   289  	// Populate the full_config_yaml and full_config_sql columns.
   290  	inheritedConfig := fullZoneConfig
   291  	if inheritedConfig == nil {
   292  		inheritedConfig = zone
   293  	}
   294  
   295  	yamlConfig, err = yaml.Marshal(inheritedConfig)
   296  	if err != nil {
   297  		return err
   298  	}
   299  	values[fullConfigYamlCol] = tree.NewDString(string(yamlConfig))
   300  
   301  	if zs == nil {
   302  		values[fullConfigSQLCol] = tree.DNull
   303  	} else {
   304  		sqlStr, err := zoneConfigToSQL(zs, inheritedConfig)
   305  		if err != nil {
   306  			return err
   307  		}
   308  		values[fullConfigSQLCol] = tree.NewDString(sqlStr)
   309  	}
   310  	return nil
   311  }
   312  
   313  // Writes a comma followed by a newline if useComma is true.
   314  func writeComma(f *tree.FmtCtx, useComma bool) {
   315  	if useComma {
   316  		f.Printf(",\n")
   317  	}
   318  }
   319  
   320  func yamlMarshalFlow(v interface{}) (string, error) {
   321  	var buf bytes.Buffer
   322  	e := yaml.NewEncoder(&buf)
   323  	e.UseStyle(yaml.FlowStyle)
   324  	if err := e.Encode(v); err != nil {
   325  		return "", err
   326  	}
   327  	if err := e.Close(); err != nil {
   328  		return "", err
   329  	}
   330  	return buf.String(), nil
   331  }
   332  
   333  // ascendZoneSpecifier logically ascends the zone hierarchy for the zone
   334  // specified by (zs, resolvedID) until the zone matching actualID is found, and
   335  // returns that zone's specifier. Results are undefined if actualID is not in
   336  // the hierarchy for (zs, resolvedID).
   337  //
   338  // Under the hood, this function encodes knowledge about the zone lookup
   339  // hierarchy to avoid performing any KV lookups, and so must be kept in sync
   340  // with GetZoneConfig.
   341  //
   342  // TODO(benesch): Teach GetZoneConfig to return the specifier of the zone it
   343  // finds without impacting performance.
   344  func ascendZoneSpecifier(
   345  	zs tree.ZoneSpecifier, resolvedID, actualID uint32, actualSubzone *zonepb.Subzone,
   346  ) tree.ZoneSpecifier {
   347  	if actualID == keys.RootNamespaceID {
   348  		// We had to traverse to the top of the hierarchy, so we're showing the
   349  		// default zone config.
   350  		zs.NamedZone = zonepb.DefaultZoneName
   351  		zs.Database = ""
   352  		zs.TableOrIndex = tree.TableIndexName{}
   353  		// Since the default zone has no partition, we can erase the
   354  		// partition name field.
   355  		zs.Partition = ""
   356  	} else if resolvedID != actualID {
   357  		// We traversed at least one level up, and we're not at the top of the
   358  		// hierarchy, so we're showing the database zone config.
   359  		zs.Database = zs.TableOrIndex.Table.CatalogName
   360  		zs.TableOrIndex = tree.TableIndexName{}
   361  		// Since databases don't have partition, we can erase the
   362  		// partition name field.
   363  		zs.Partition = ""
   364  	} else if actualSubzone == nil {
   365  		// We didn't find a subzone, so no index or partition zone config exists.
   366  		zs.TableOrIndex.Index = ""
   367  		zs.Partition = ""
   368  	} else if actualSubzone.PartitionName == "" {
   369  		// The resolved subzone did not name a partition, just an index.
   370  		zs.Partition = ""
   371  	}
   372  	return zs
   373  }