vitess.io/vitess@v0.16.2/go/vt/vtgate/planbuilder/show.go (about) 1 /* 2 Copyright 2020 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package planbuilder 18 19 import ( 20 "fmt" 21 "regexp" 22 "sort" 23 "strings" 24 25 vschemapb "vitess.io/vitess/go/vt/proto/vschema" 26 27 "vitess.io/vitess/go/mysql/collations" 28 "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" 29 30 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 31 "vitess.io/vitess/go/vt/vtgate/vindexes" 32 33 "vitess.io/vitess/go/sqltypes" 34 "vitess.io/vitess/go/vt/key" 35 querypb "vitess.io/vitess/go/vt/proto/query" 36 "vitess.io/vitess/go/vt/sqlparser" 37 "vitess.io/vitess/go/vt/vterrors" 38 "vitess.io/vitess/go/vt/vtgate/engine" 39 ) 40 41 const ( 42 utf8 = "utf8" 43 utf8mb4 = "utf8mb4" 44 both = "both" 45 charset = "charset" 46 ) 47 48 func buildShowPlan(sql string, stmt *sqlparser.Show, _ *sqlparser.ReservedVars, vschema plancontext.VSchema) (*planResult, error) { 49 if vschema.Destination() != nil { 50 return buildByPassDDLPlan(sql, vschema) 51 } 52 53 var prim engine.Primitive 54 var err error 55 switch show := stmt.Internal.(type) { 56 case *sqlparser.ShowBasic: 57 prim, err = buildShowBasicPlan(show, vschema) 58 case *sqlparser.ShowCreate: 59 prim, err = buildShowCreatePlan(show, vschema) 60 case *sqlparser.ShowOther: 61 prim, err = buildShowOtherPlan(sql, vschema) 62 default: 63 return nil, vterrors.VT13001(fmt.Sprintf("undefined SHOW type: %T", stmt.Internal)) 64 } 65 if err != nil { 66 return nil, err 67 } 68 69 return newPlanResult(prim), nil 70 } 71 72 func buildShowOtherPlan(sql string, vschema plancontext.VSchema) (engine.Primitive, error) { 73 ks, err := vschema.AnyKeyspace() 74 if err != nil { 75 return nil, err 76 } 77 return &engine.Send{ 78 Keyspace: ks, 79 TargetDestination: key.DestinationAnyShard{}, 80 Query: sql, 81 SingleShardOnly: true, 82 }, nil 83 } 84 85 func buildShowBasicPlan(show *sqlparser.ShowBasic, vschema plancontext.VSchema) (engine.Primitive, error) { 86 switch show.Command { 87 case sqlparser.Charset: 88 return buildCharsetPlan(show) 89 case sqlparser.Collation, sqlparser.Function, sqlparser.Privilege, sqlparser.Procedure: 90 return buildSendAnywherePlan(show, vschema) 91 case sqlparser.VariableGlobal, sqlparser.VariableSession: 92 return buildVariablePlan(show, vschema) 93 case sqlparser.Column, sqlparser.Index: 94 return buildShowTblPlan(show, vschema) 95 case sqlparser.Database, sqlparser.Keyspace: 96 return buildDBPlan(show, vschema) 97 case sqlparser.OpenTable, sqlparser.TableStatus, sqlparser.Table, sqlparser.Trigger: 98 return buildPlanWithDB(show, vschema) 99 case sqlparser.StatusGlobal, sqlparser.StatusSession: 100 return buildSendAnywherePlan(show, vschema) 101 case sqlparser.VitessMigrations: 102 return buildShowVMigrationsPlan(show, vschema) 103 case sqlparser.VGtidExecGlobal: 104 return buildShowVGtidPlan(show, vschema) 105 case sqlparser.GtidExecGlobal: 106 return buildShowGtidPlan(show, vschema) 107 case sqlparser.Warnings: 108 return buildWarnings() 109 case sqlparser.Plugins: 110 return buildPluginsPlan() 111 case sqlparser.Engines: 112 return buildEnginesPlan() 113 case sqlparser.VitessReplicationStatus, sqlparser.VitessShards, sqlparser.VitessTablets, sqlparser.VitessVariables: 114 return &engine.ShowExec{ 115 Command: show.Command, 116 ShowFilter: show.Filter, 117 }, nil 118 case sqlparser.VitessTarget: 119 return buildShowTargetPlan(vschema) 120 case sqlparser.VschemaTables: 121 return buildVschemaTablesPlan(vschema) 122 case sqlparser.VschemaVindexes: 123 return buildVschemaVindexesPlan(show, vschema) 124 } 125 return nil, vterrors.VT13001(fmt.Sprintf("unknown SHOW query type %s", show.Command.ToString())) 126 127 } 128 129 func buildShowTargetPlan(vschema plancontext.VSchema) (engine.Primitive, error) { 130 rows := [][]sqltypes.Value{buildVarCharRow(vschema.TargetString())} 131 return engine.NewRowsPrimitive(rows, 132 buildVarCharFields("Target")), nil 133 } 134 135 func buildCharsetPlan(show *sqlparser.ShowBasic) (engine.Primitive, error) { 136 fields := buildVarCharFields("Charset", "Description", "Default collation") 137 maxLenField := &querypb.Field{Name: "Maxlen", Type: sqltypes.Int32} 138 fields = append(fields, maxLenField) 139 140 charsets := []string{utf8, utf8mb4} 141 rows, err := generateCharsetRows(show.Filter, charsets) 142 if err != nil { 143 return nil, err 144 } 145 146 return engine.NewRowsPrimitive(rows, fields), nil 147 } 148 149 func buildSendAnywherePlan(show *sqlparser.ShowBasic, vschema plancontext.VSchema) (engine.Primitive, error) { 150 ks, err := vschema.AnyKeyspace() 151 if err != nil { 152 return nil, err 153 } 154 return &engine.Send{ 155 Keyspace: ks, 156 TargetDestination: key.DestinationAnyShard{}, 157 Query: sqlparser.String(show), 158 IsDML: false, 159 SingleShardOnly: true, 160 }, nil 161 } 162 163 func buildVariablePlan(show *sqlparser.ShowBasic, vschema plancontext.VSchema) (engine.Primitive, error) { 164 plan, err := buildSendAnywherePlan(show, vschema) 165 if err != nil { 166 return nil, err 167 } 168 plan = engine.NewReplaceVariables(plan) 169 return plan, nil 170 } 171 172 func buildShowTblPlan(show *sqlparser.ShowBasic, vschema plancontext.VSchema) (engine.Primitive, error) { 173 if !show.DbName.IsEmpty() { 174 show.Tbl.Qualifier = sqlparser.NewIdentifierCS(show.DbName.String()) 175 // Remove Database Name from the query. 176 show.DbName = sqlparser.NewIdentifierCS("") 177 } 178 179 dest := key.Destination(key.DestinationAnyShard{}) 180 var ks *vindexes.Keyspace 181 var err error 182 183 if !show.Tbl.Qualifier.IsEmpty() && sqlparser.SystemSchema(show.Tbl.Qualifier.String()) { 184 ks, err = vschema.AnyKeyspace() 185 if err != nil { 186 return nil, err 187 } 188 } else { 189 table, _, _, _, destination, err := vschema.FindTableOrVindex(show.Tbl) 190 if err != nil { 191 return nil, err 192 } 193 if table == nil { 194 return nil, vterrors.VT05004(show.Tbl.Name.String()) 195 } 196 // Update the table. 197 show.Tbl.Qualifier = sqlparser.NewIdentifierCS("") 198 show.Tbl.Name = table.Name 199 200 if destination != nil { 201 dest = destination 202 } 203 ks = table.Keyspace 204 } 205 206 return &engine.Send{ 207 Keyspace: ks, 208 TargetDestination: dest, 209 Query: sqlparser.String(show), 210 IsDML: false, 211 SingleShardOnly: true, 212 }, nil 213 } 214 215 func buildDBPlan(show *sqlparser.ShowBasic, vschema plancontext.VSchema) (engine.Primitive, error) { 216 ks, err := vschema.AllKeyspace() 217 if err != nil { 218 return nil, err 219 } 220 221 var filter *regexp.Regexp 222 223 if show.Filter != nil { 224 filter = sqlparser.LikeToRegexp(show.Filter.Like) 225 } 226 227 if filter == nil { 228 filter = regexp.MustCompile(".*") 229 } 230 231 // rows := make([][]sqltypes.Value, 0, len(ks)+4) 232 var rows [][]sqltypes.Value 233 234 if show.Command == sqlparser.Database { 235 // Hard code default databases 236 ks = append(ks, &vindexes.Keyspace{Name: "information_schema"}, 237 &vindexes.Keyspace{Name: "mysql"}, 238 &vindexes.Keyspace{Name: "sys"}, 239 &vindexes.Keyspace{Name: "performance_schema"}) 240 } 241 242 for _, v := range ks { 243 if filter.MatchString(v.Name) { 244 rows = append(rows, buildVarCharRow(v.Name)) 245 } 246 } 247 return engine.NewRowsPrimitive(rows, buildVarCharFields("Database")), nil 248 } 249 250 // buildShowVMigrationsPlan serves `SHOW VITESS_MIGRATIONS ...` queries. It invokes queries on _vt.schema_migrations on all PRIMARY tablets on keyspace's shards. 251 func buildShowVMigrationsPlan(show *sqlparser.ShowBasic, vschema plancontext.VSchema) (engine.Primitive, error) { 252 dest, ks, tabletType, err := vschema.TargetDestination(show.DbName.String()) 253 if err != nil { 254 return nil, err 255 } 256 if ks == nil { 257 return nil, vterrors.VT09005() 258 } 259 260 if tabletType != topodatapb.TabletType_PRIMARY { 261 return nil, vterrors.VT09006("SHOW") 262 } 263 264 if dest == nil { 265 dest = key.DestinationAllShards{} 266 } 267 268 sql := "SELECT * FROM _vt.schema_migrations" 269 270 if show.Filter != nil { 271 if show.Filter.Filter != nil { 272 sql += fmt.Sprintf(" where %s", sqlparser.String(show.Filter.Filter)) 273 } else if show.Filter.Like != "" { 274 lit := sqlparser.String(sqlparser.NewStrLiteral(show.Filter.Like)) 275 sql += fmt.Sprintf(" where migration_uuid LIKE %s OR migration_context LIKE %s OR migration_status LIKE %s", lit, lit, lit) 276 } 277 } 278 return &engine.Send{ 279 Keyspace: ks, 280 TargetDestination: dest, 281 Query: sql, 282 }, nil 283 } 284 285 func buildPlanWithDB(show *sqlparser.ShowBasic, vschema plancontext.VSchema) (engine.Primitive, error) { 286 dbName := show.DbName 287 dbDestination := show.DbName.String() 288 if sqlparser.SystemSchema(dbDestination) { 289 ks, err := vschema.AnyKeyspace() 290 if err != nil { 291 return nil, err 292 } 293 dbDestination = ks.Name 294 } else { 295 // Remove Database Name from the query. 296 show.DbName = sqlparser.NewIdentifierCS("") 297 } 298 destination, keyspace, _, err := vschema.TargetDestination(dbDestination) 299 if err != nil { 300 return nil, err 301 } 302 if destination == nil { 303 destination = key.DestinationAnyShard{} 304 } 305 306 if dbName.IsEmpty() { 307 dbName = sqlparser.NewIdentifierCS(keyspace.Name) 308 } 309 310 query := sqlparser.String(show) 311 var plan engine.Primitive 312 plan = &engine.Send{ 313 Keyspace: keyspace, 314 TargetDestination: destination, 315 Query: query, 316 IsDML: false, 317 SingleShardOnly: true, 318 } 319 if show.Command == sqlparser.Table { 320 plan, err = engine.NewRenameField([]string{"Tables_in_" + dbName.String()}, []int{0}, plan) 321 if err != nil { 322 return nil, err 323 } 324 } 325 return plan, nil 326 327 } 328 329 func buildVarCharFields(names ...string) []*querypb.Field { 330 fields := make([]*querypb.Field, len(names)) 331 for i, v := range names { 332 fields[i] = &querypb.Field{ 333 Name: v, 334 Type: sqltypes.VarChar, 335 Charset: collations.CollationUtf8ID, 336 Flags: uint32(querypb.MySqlFlag_NOT_NULL_FLAG), 337 } 338 } 339 return fields 340 } 341 342 func buildVarCharRow(values ...string) []sqltypes.Value { 343 row := make([]sqltypes.Value, len(values)) 344 for i, v := range values { 345 row[i] = sqltypes.NewVarChar(v) 346 } 347 return row 348 } 349 350 func generateCharsetRows(showFilter *sqlparser.ShowFilter, colNames []string) ([][]sqltypes.Value, error) { 351 if showFilter == nil { 352 return buildCharsetRows(both), nil 353 } 354 355 var filteredColName string 356 var err error 357 358 if showFilter.Like != "" { 359 filteredColName, err = checkLikeOpt(showFilter.Like, colNames) 360 if err != nil { 361 return nil, err 362 } 363 364 } else { 365 cmpExp, ok := showFilter.Filter.(*sqlparser.ComparisonExpr) 366 if !ok { 367 return nil, vterrors.VT12001("expect a 'LIKE' or '=' expression") 368 } 369 370 left, ok := cmpExp.Left.(*sqlparser.ColName) 371 if !ok { 372 return nil, vterrors.VT12001("expect left side to be 'charset'") 373 } 374 leftOk := left.Name.EqualString(charset) 375 376 if leftOk { 377 literal, ok := cmpExp.Right.(*sqlparser.Literal) 378 if !ok { 379 return nil, vterrors.VT12001("we expect the right side to be a string") 380 } 381 rightString := literal.Val 382 383 switch cmpExp.Operator { 384 case sqlparser.EqualOp: 385 for _, colName := range colNames { 386 if rightString == colName { 387 filteredColName = colName 388 } 389 } 390 case sqlparser.LikeOp: 391 filteredColName, err = checkLikeOpt(rightString, colNames) 392 if err != nil { 393 return nil, err 394 } 395 } 396 } 397 398 } 399 400 return buildCharsetRows(filteredColName), nil 401 } 402 403 func buildCharsetRows(colName string) [][]sqltypes.Value { 404 row0 := buildVarCharRow( 405 "utf8", 406 "UTF-8 Unicode", 407 "utf8_general_ci") 408 row0 = append(row0, sqltypes.NewInt32(3)) 409 row1 := buildVarCharRow( 410 "utf8mb4", 411 "UTF-8 Unicode", 412 "utf8mb4_general_ci") 413 row1 = append(row1, sqltypes.NewInt32(4)) 414 415 switch colName { 416 case utf8: 417 return [][]sqltypes.Value{row0} 418 case utf8mb4: 419 return [][]sqltypes.Value{row1} 420 case both: 421 return [][]sqltypes.Value{row0, row1} 422 } 423 424 return [][]sqltypes.Value{} 425 } 426 427 func checkLikeOpt(likeOpt string, colNames []string) (string, error) { 428 likeRegexp := strings.ReplaceAll(likeOpt, "%", ".*") 429 for _, v := range colNames { 430 match, err := regexp.MatchString(likeRegexp, v) 431 if err != nil { 432 return "", err 433 } 434 if match { 435 return v, nil 436 } 437 } 438 439 return "", nil 440 } 441 442 func buildShowCreatePlan(show *sqlparser.ShowCreate, vschema plancontext.VSchema) (engine.Primitive, error) { 443 switch show.Command { 444 case sqlparser.CreateDb: 445 return buildCreateDbPlan(show, vschema) 446 case sqlparser.CreateE, sqlparser.CreateF, sqlparser.CreateProc, sqlparser.CreateTr, sqlparser.CreateV: 447 return buildCreatePlan(show, vschema) 448 case sqlparser.CreateTbl: 449 return buildCreateTblPlan(show, vschema) 450 } 451 return nil, vterrors.VT13001("unknown SHOW query type %s", show.Command.ToString()) 452 } 453 454 func buildCreateDbPlan(show *sqlparser.ShowCreate, vschema plancontext.VSchema) (engine.Primitive, error) { 455 dbName := show.Op.Name.String() 456 if sqlparser.SystemSchema(dbName) { 457 ks, err := vschema.AnyKeyspace() 458 if err != nil { 459 return nil, err 460 } 461 dbName = ks.Name 462 } 463 464 dest, ks, _, err := vschema.TargetDestination(dbName) 465 if err != nil { 466 return nil, err 467 } 468 469 if dest == nil { 470 dest = key.DestinationAnyShard{} 471 } 472 473 return &engine.Send{ 474 Keyspace: ks, 475 TargetDestination: dest, 476 Query: sqlparser.String(show), 477 IsDML: false, 478 SingleShardOnly: true, 479 }, nil 480 } 481 482 func buildCreateTblPlan(show *sqlparser.ShowCreate, vschema plancontext.VSchema) (engine.Primitive, error) { 483 dest := key.Destination(key.DestinationAnyShard{}) 484 var ks *vindexes.Keyspace 485 var err error 486 487 if !show.Op.Qualifier.IsEmpty() && sqlparser.SystemSchema(show.Op.Qualifier.String()) { 488 ks, err = vschema.AnyKeyspace() 489 if err != nil { 490 return nil, err 491 } 492 } else { 493 tbl, _, _, _, destKs, err := vschema.FindTableOrVindex(show.Op) 494 if err != nil { 495 return nil, err 496 } 497 if tbl == nil { 498 return nil, vterrors.VT05004(sqlparser.String(show.Op)) 499 } 500 ks = tbl.Keyspace 501 if destKs != nil { 502 dest = destKs 503 } 504 show.Op.Qualifier = sqlparser.NewIdentifierCS("") 505 show.Op.Name = tbl.Name 506 } 507 508 return &engine.Send{ 509 Keyspace: ks, 510 TargetDestination: dest, 511 Query: sqlparser.String(show), 512 IsDML: false, 513 SingleShardOnly: true, 514 }, nil 515 516 } 517 518 func buildCreatePlan(show *sqlparser.ShowCreate, vschema plancontext.VSchema) (engine.Primitive, error) { 519 dbName := "" 520 if !show.Op.Qualifier.IsEmpty() { 521 dbName = show.Op.Qualifier.String() 522 } 523 524 if sqlparser.SystemSchema(dbName) { 525 ks, err := vschema.AnyKeyspace() 526 if err != nil { 527 return nil, err 528 } 529 dbName = ks.Name 530 } else { 531 show.Op.Qualifier = sqlparser.NewIdentifierCS("") 532 } 533 534 dest, ks, _, err := vschema.TargetDestination(dbName) 535 if err != nil { 536 return nil, err 537 } 538 if dest == nil { 539 dest = key.DestinationAnyShard{} 540 } 541 542 return &engine.Send{ 543 Keyspace: ks, 544 TargetDestination: dest, 545 Query: sqlparser.String(show), 546 IsDML: false, 547 SingleShardOnly: true, 548 }, nil 549 550 } 551 552 func buildShowVGtidPlan(show *sqlparser.ShowBasic, vschema plancontext.VSchema) (engine.Primitive, error) { 553 send, err := buildShowGtidPlan(show, vschema) 554 if err != nil { 555 return nil, err 556 } 557 return &engine.OrderedAggregate{ 558 PreProcess: true, 559 Aggregates: []*engine.AggregateParams{ 560 { 561 Opcode: engine.AggregateGtid, 562 Col: 1, 563 Alias: "global vgtid_executed", 564 }, 565 }, 566 TruncateColumnCount: 2, 567 Input: send, 568 }, nil 569 } 570 571 func buildShowGtidPlan(show *sqlparser.ShowBasic, vschema plancontext.VSchema) (engine.Primitive, error) { 572 dbName := "" 573 if !show.DbName.IsEmpty() { 574 dbName = show.DbName.String() 575 } 576 dest, ks, _, err := vschema.TargetDestination(dbName) 577 if err != nil { 578 return nil, err 579 } 580 if dest == nil { 581 dest = key.DestinationAllShards{} 582 } 583 584 return &engine.Send{ 585 Keyspace: ks, 586 TargetDestination: dest, 587 Query: fmt.Sprintf(`select '%s' as db_name, @@global.gtid_executed as gtid_executed, :%s as shard`, ks.Name, engine.ShardName), 588 ShardNameNeeded: true, 589 }, nil 590 } 591 592 func buildWarnings() (engine.Primitive, error) { 593 594 f := func(sa engine.SessionActions) (*sqltypes.Result, error) { 595 fields := []*querypb.Field{ 596 {Name: "Level", Type: sqltypes.VarChar}, 597 {Name: "Code", Type: sqltypes.Uint16}, 598 {Name: "Message", Type: sqltypes.VarChar}, 599 } 600 601 warns := sa.GetWarnings() 602 rows := make([][]sqltypes.Value, 0, len(warns)) 603 604 for _, warn := range warns { 605 rows = append(rows, []sqltypes.Value{ 606 sqltypes.NewVarChar("Warning"), 607 sqltypes.NewUint32(warn.Code), 608 sqltypes.NewVarChar(warn.Message), 609 }) 610 } 611 return &sqltypes.Result{ 612 Fields: fields, 613 Rows: rows, 614 }, nil 615 } 616 617 return engine.NewSessionPrimitive("SHOW WARNINGS", f), nil 618 } 619 620 func buildPluginsPlan() (engine.Primitive, error) { 621 var rows [][]sqltypes.Value 622 rows = append(rows, buildVarCharRow( 623 "InnoDB", 624 "ACTIVE", 625 "STORAGE ENGINE", 626 "NULL", 627 "GPL")) 628 629 return engine.NewRowsPrimitive(rows, 630 buildVarCharFields("Name", "Status", "Type", "Library", "License")), nil 631 } 632 633 func buildEnginesPlan() (engine.Primitive, error) { 634 var rows [][]sqltypes.Value 635 rows = append(rows, buildVarCharRow( 636 "InnoDB", 637 "DEFAULT", 638 "Supports transactions, row-level locking, and foreign keys", 639 "YES", 640 "YES", 641 "YES")) 642 643 return engine.NewRowsPrimitive(rows, 644 buildVarCharFields("Engine", "Support", "Comment", "Transactions", "XA", "Savepoints")), nil 645 } 646 647 func buildVschemaTablesPlan(vschema plancontext.VSchema) (engine.Primitive, error) { 648 vs := vschema.GetVSchema() 649 ks, err := vschema.DefaultKeyspace() 650 if err != nil { 651 return nil, err 652 } 653 schemaKs, ok := vs.Keyspaces[ks.Name] 654 if !ok { 655 return nil, vterrors.VT05003(ks.Name) 656 } 657 658 var tables []string 659 for name := range schemaKs.Tables { 660 tables = append(tables, name) 661 } 662 sort.Strings(tables) 663 664 rows := make([][]sqltypes.Value, len(tables)) 665 for i, v := range tables { 666 rows[i] = buildVarCharRow(v) 667 } 668 669 return engine.NewRowsPrimitive(rows, buildVarCharFields("Tables")), nil 670 } 671 672 func buildVschemaVindexesPlan(show *sqlparser.ShowBasic, vschema plancontext.VSchema) (engine.Primitive, error) { 673 vs := vschema.GetSrvVschema() 674 rows := make([][]sqltypes.Value, 0, 16) 675 676 if !show.Tbl.IsEmpty() { 677 _, ks, _, err := vschema.TargetDestination(show.Tbl.Qualifier.String()) 678 if err != nil { 679 return nil, err 680 } 681 var schemaKs *vschemapb.Keyspace 682 var tbl *vschemapb.Table 683 if !ks.Sharded { 684 tbl = &vschemapb.Table{} 685 } else { 686 schemaKs = vs.Keyspaces[ks.Name] 687 tableName := show.Tbl.Name.String() 688 schemaTbl, ok := schemaKs.Tables[tableName] 689 if !ok { 690 return nil, vterrors.VT05005(tableName, ks.Name) 691 } 692 tbl = schemaTbl 693 } 694 695 for _, colVindex := range tbl.ColumnVindexes { 696 vindex, ok := schemaKs.Vindexes[colVindex.GetName()] 697 columns := colVindex.GetColumns() 698 if len(columns) == 0 { 699 columns = []string{colVindex.GetColumn()} 700 } 701 if ok { 702 params := make([]string, 0, 4) 703 for k, v := range vindex.GetParams() { 704 params = append(params, fmt.Sprintf("%s=%s", k, v)) 705 } 706 sort.Strings(params) 707 rows = append(rows, buildVarCharRow(strings.Join(columns, ", "), colVindex.GetName(), vindex.GetType(), strings.Join(params, "; "), vindex.GetOwner())) 708 } else { 709 rows = append(rows, buildVarCharRow(strings.Join(columns, ", "), colVindex.GetName(), "", "", "")) 710 } 711 } 712 713 return engine.NewRowsPrimitive(rows, 714 buildVarCharFields("Columns", "Name", "Type", "Params", "Owner"), 715 ), nil 716 } 717 718 // For the query interface to be stable we need to sort 719 // for each of the map iterations 720 ksNames := make([]string, 0, len(vs.Keyspaces)) 721 for name := range vs.Keyspaces { 722 ksNames = append(ksNames, name) 723 } 724 sort.Strings(ksNames) 725 for _, ksName := range ksNames { 726 ks := vs.Keyspaces[ksName] 727 728 vindexNames := make([]string, 0, len(ks.Vindexes)) 729 for name := range ks.Vindexes { 730 vindexNames = append(vindexNames, name) 731 } 732 sort.Strings(vindexNames) 733 for _, vindexName := range vindexNames { 734 vindex := ks.Vindexes[vindexName] 735 736 params := make([]string, 0, 4) 737 for k, v := range vindex.GetParams() { 738 params = append(params, fmt.Sprintf("%s=%s", k, v)) 739 } 740 sort.Strings(params) 741 rows = append(rows, buildVarCharRow(ksName, vindexName, vindex.GetType(), strings.Join(params, "; "), vindex.GetOwner())) 742 } 743 } 744 return engine.NewRowsPrimitive(rows, 745 buildVarCharFields("Keyspace", "Name", "Type", "Params", "Owner"), 746 ), nil 747 748 }