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 }