vitess.io/vitess@v0.16.2/go/vt/vtexplain/vtexplain_vttablet.go (about) 1 /* 2 Copyright 2019 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 vtexplain 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "reflect" 24 "strings" 25 "sync" 26 27 "vitess.io/vitess/go/vt/sidecardb" 28 29 "vitess.io/vitess/go/mysql" 30 "vitess.io/vitess/go/mysql/collations" 31 "vitess.io/vitess/go/mysql/fakesqldb" 32 "vitess.io/vitess/go/sqlescape" 33 "vitess.io/vitess/go/sqltypes" 34 "vitess.io/vitess/go/vt/dbconfigs" 35 "vitess.io/vitess/go/vt/log" 36 "vitess.io/vitess/go/vt/mysqlctl" 37 "vitess.io/vitess/go/vt/sqlparser" 38 "vitess.io/vitess/go/vt/topo/memorytopo" 39 "vitess.io/vitess/go/vt/topo/topoproto" 40 "vitess.io/vitess/go/vt/vtgate/evalengine" 41 42 "vitess.io/vitess/go/vt/vttablet/queryservice" 43 "vitess.io/vitess/go/vt/vttablet/tabletserver" 44 "vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv" 45 46 querypb "vitess.io/vitess/go/vt/proto/query" 47 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 48 ) 49 50 type tabletEnv struct { 51 // map of schema introspection queries to their expected results 52 schemaQueries map[string]*sqltypes.Result 53 54 // map for each table from the column name to its type 55 tableColumns map[string]map[string]querypb.Type 56 } 57 58 func newTabletEnv() *tabletEnv { 59 return &tabletEnv{ 60 schemaQueries: make(map[string]*sqltypes.Result), 61 tableColumns: make(map[string]map[string]querypb.Type), 62 } 63 } 64 65 func (te *tabletEnv) addResult(query string, result *sqltypes.Result) { 66 te.schemaQueries[query] = result 67 } 68 69 func (te *tabletEnv) getResult(query string) *sqltypes.Result { 70 result, ok := te.schemaQueries[query] 71 if !ok { 72 return nil 73 } 74 return result 75 } 76 77 func (vte *VTExplain) setGlobalTabletEnv(env *tabletEnv) { 78 vte.globalTabletEnv = env 79 } 80 81 func (vte *VTExplain) getGlobalTabletEnv() *tabletEnv { 82 return vte.globalTabletEnv 83 } 84 85 // explainTablet is the query service that simulates a tablet. 86 // 87 // To avoid needing to boilerplate implement the unneeded portions of the 88 // QueryService interface, it overrides only the necessary methods and embeds 89 // a wrapped QueryService that throws an error if an unimplemented method is 90 // called. 91 type explainTablet struct { 92 queryservice.QueryService 93 94 db *fakesqldb.DB 95 tsv *tabletserver.TabletServer 96 97 mu sync.Mutex 98 tabletQueries []*TabletQuery 99 mysqlQueries []*MysqlQuery 100 currentTime int 101 vte *VTExplain 102 } 103 104 var _ queryservice.QueryService = (*explainTablet)(nil) 105 106 func (vte *VTExplain) newTablet(opts *Options, t *topodatapb.Tablet) *explainTablet { 107 db := fakesqldb.New(nil) 108 sidecardb.AddSchemaInitQueries(db, true) 109 110 config := tabletenv.NewCurrentConfig() 111 config.TrackSchemaVersions = false 112 if opts.ExecutionMode == ModeTwoPC { 113 config.TwoPCCoordinatorAddress = "XXX" 114 config.TwoPCAbandonAge = 1.0 115 config.TwoPCEnable = true 116 } 117 config.EnableOnlineDDL = false 118 config.EnableTableGC = false 119 120 // XXX much of this is cloned from the tabletserver tests 121 tsv := tabletserver.NewTabletServer(topoproto.TabletAliasString(t.Alias), config, memorytopo.NewServer(""), t.Alias) 122 123 tablet := explainTablet{db: db, tsv: tsv, vte: vte} 124 db.Handler = &tablet 125 126 tablet.QueryService = queryservice.Wrap( 127 nil, 128 func(ctx context.Context, target *querypb.Target, conn queryservice.QueryService, name string, inTransaction bool, inner func(context.Context, *querypb.Target, queryservice.QueryService) (bool, error)) error { 129 return fmt.Errorf("explainTablet does not implement %s", name) 130 }, 131 ) 132 133 params, _ := db.ConnParams().MysqlParams() 134 cp := *params 135 dbcfgs := dbconfigs.NewTestDBConfigs(cp, cp, "") 136 cnf := mysqlctl.NewMycnf(22222, 6802) 137 cnf.ServerID = 33333 138 139 target := querypb.Target{ 140 Keyspace: t.Keyspace, 141 Shard: t.Shard, 142 TabletType: topodatapb.TabletType_PRIMARY, 143 } 144 tsv.StartService(&target, dbcfgs, nil /* mysqld */) 145 146 // clear all the schema initialization queries out of the tablet 147 // to avoid cluttering the output 148 tablet.mysqlQueries = nil 149 150 return &tablet 151 } 152 153 var _ queryservice.QueryService = (*explainTablet)(nil) // compile-time interface check 154 155 // Begin is part of the QueryService interface. 156 func (t *explainTablet) Begin(ctx context.Context, target *querypb.Target, options *querypb.ExecuteOptions) (queryservice.TransactionState, error) { 157 t.mu.Lock() 158 t.currentTime = t.vte.batchTime.Wait() 159 t.tabletQueries = append(t.tabletQueries, &TabletQuery{ 160 Time: t.currentTime, 161 SQL: "begin", 162 }) 163 164 t.mu.Unlock() 165 166 return t.tsv.Begin(ctx, target, options) 167 } 168 169 // Commit is part of the QueryService interface. 170 func (t *explainTablet) Commit(ctx context.Context, target *querypb.Target, transactionID int64) (int64, error) { 171 t.mu.Lock() 172 t.currentTime = t.vte.batchTime.Wait() 173 t.tabletQueries = append(t.tabletQueries, &TabletQuery{ 174 Time: t.currentTime, 175 SQL: "commit", 176 }) 177 t.mu.Unlock() 178 179 return t.tsv.Commit(ctx, target, transactionID) 180 } 181 182 // Rollback is part of the QueryService interface. 183 func (t *explainTablet) Rollback(ctx context.Context, target *querypb.Target, transactionID int64) (int64, error) { 184 t.mu.Lock() 185 t.currentTime = t.vte.batchTime.Wait() 186 t.mu.Unlock() 187 return t.tsv.Rollback(ctx, target, transactionID) 188 } 189 190 // Execute is part of the QueryService interface. 191 func (t *explainTablet) Execute(ctx context.Context, target *querypb.Target, sql string, bindVariables map[string]*querypb.BindVariable, transactionID, reservedID int64, options *querypb.ExecuteOptions) (*sqltypes.Result, error) { 192 t.mu.Lock() 193 t.currentTime = t.vte.batchTime.Wait() 194 195 // Since the query is simulated being "sent" over the wire we need to 196 // copy the bindVars into the executor to avoid a data race. 197 bindVariables = sqltypes.CopyBindVariables(bindVariables) 198 t.tabletQueries = append(t.tabletQueries, &TabletQuery{ 199 Time: t.currentTime, 200 SQL: sql, 201 BindVars: bindVariables, 202 }) 203 t.mu.Unlock() 204 205 return t.tsv.Execute(ctx, target, sql, bindVariables, transactionID, reservedID, options) 206 } 207 208 // Prepare is part of the QueryService interface. 209 func (t *explainTablet) Prepare(ctx context.Context, target *querypb.Target, transactionID int64, dtid string) (err error) { 210 t.mu.Lock() 211 t.currentTime = t.vte.batchTime.Wait() 212 t.mu.Unlock() 213 return t.tsv.Prepare(ctx, target, transactionID, dtid) 214 } 215 216 // CommitPrepared commits the prepared transaction. 217 func (t *explainTablet) CommitPrepared(ctx context.Context, target *querypb.Target, dtid string) (err error) { 218 t.mu.Lock() 219 t.currentTime = t.vte.batchTime.Wait() 220 t.mu.Unlock() 221 return t.tsv.CommitPrepared(ctx, target, dtid) 222 } 223 224 // CreateTransaction is part of the QueryService interface. 225 func (t *explainTablet) CreateTransaction(ctx context.Context, target *querypb.Target, dtid string, participants []*querypb.Target) (err error) { 226 t.mu.Lock() 227 t.currentTime = t.vte.batchTime.Wait() 228 t.mu.Unlock() 229 return t.tsv.CreateTransaction(ctx, target, dtid, participants) 230 } 231 232 // StartCommit is part of the QueryService interface. 233 func (t *explainTablet) StartCommit(ctx context.Context, target *querypb.Target, transactionID int64, dtid string) (err error) { 234 t.mu.Lock() 235 t.currentTime = t.vte.batchTime.Wait() 236 t.mu.Unlock() 237 return t.tsv.StartCommit(ctx, target, transactionID, dtid) 238 } 239 240 // SetRollback is part of the QueryService interface. 241 func (t *explainTablet) SetRollback(ctx context.Context, target *querypb.Target, dtid string, transactionID int64) (err error) { 242 t.mu.Lock() 243 t.currentTime = t.vte.batchTime.Wait() 244 t.mu.Unlock() 245 return t.tsv.SetRollback(ctx, target, dtid, transactionID) 246 } 247 248 // ConcludeTransaction is part of the QueryService interface. 249 func (t *explainTablet) ConcludeTransaction(ctx context.Context, target *querypb.Target, dtid string) (err error) { 250 t.mu.Lock() 251 t.currentTime = t.vte.batchTime.Wait() 252 t.mu.Unlock() 253 return t.tsv.ConcludeTransaction(ctx, target, dtid) 254 } 255 256 // ReadTransaction is part of the QueryService interface. 257 func (t *explainTablet) ReadTransaction(ctx context.Context, target *querypb.Target, dtid string) (metadata *querypb.TransactionMetadata, err error) { 258 t.mu.Lock() 259 t.currentTime = t.vte.batchTime.Wait() 260 t.mu.Unlock() 261 return t.tsv.ReadTransaction(ctx, target, dtid) 262 } 263 264 // BeginExecute is part of the QueryService interface. 265 func (t *explainTablet) BeginExecute(ctx context.Context, target *querypb.Target, preQueries []string, sql string, bindVariables map[string]*querypb.BindVariable, reservedID int64, options *querypb.ExecuteOptions) (queryservice.TransactionState, *sqltypes.Result, error) { 266 t.mu.Lock() 267 t.currentTime = t.vte.batchTime.Wait() 268 bindVariables = sqltypes.CopyBindVariables(bindVariables) 269 t.tabletQueries = append(t.tabletQueries, &TabletQuery{ 270 Time: t.currentTime, 271 SQL: sql, 272 BindVars: bindVariables, 273 }) 274 t.mu.Unlock() 275 276 return t.tsv.BeginExecute(ctx, target, preQueries, sql, bindVariables, reservedID, options) 277 } 278 279 // Close is part of the QueryService interface. 280 func (t *explainTablet) Close(ctx context.Context) error { 281 return t.tsv.Close(ctx) 282 } 283 284 func newTabletEnvironment(ddls []sqlparser.DDLStatement, opts *Options) (*tabletEnv, error) { 285 tEnv := newTabletEnv() 286 schemaQueries := map[string]*sqltypes.Result{ 287 "select unix_timestamp()": { 288 Fields: []*querypb.Field{{ 289 Type: sqltypes.Uint64, 290 }}, 291 Rows: [][]sqltypes.Value{ 292 {sqltypes.NewInt32(1427325875)}, 293 }, 294 }, 295 "select @@global.sql_mode": { 296 Fields: []*querypb.Field{{ 297 Type: sqltypes.VarChar, 298 }}, 299 Rows: [][]sqltypes.Value{ 300 {sqltypes.NewVarBinary("STRICT_TRANS_TABLES")}, 301 }, 302 }, 303 "select @@session.sql_mode as sql_mode": { 304 Fields: []*querypb.Field{{ 305 Name: "sql_mode", 306 Type: sqltypes.VarChar, 307 }}, 308 Rows: [][]sqltypes.Value{ 309 {sqltypes.NewVarBinary("STRICT_TRANS_TABLES")}, 310 }, 311 }, 312 "select @@autocommit": { 313 Fields: []*querypb.Field{{ 314 Type: sqltypes.Uint64, 315 }}, 316 Rows: [][]sqltypes.Value{ 317 {sqltypes.NewVarBinary("1")}, 318 }, 319 }, 320 "select @@sql_auto_is_null": { 321 Fields: []*querypb.Field{{ 322 Type: sqltypes.Uint64, 323 }}, 324 Rows: [][]sqltypes.Value{ 325 {sqltypes.NewVarBinary("0")}, 326 }, 327 }, 328 "set @@session.sql_log_bin = 0": { 329 Fields: []*querypb.Field{{ 330 Type: sqltypes.Uint64, 331 }}, 332 Rows: [][]sqltypes.Value{}, 333 }, 334 "create database if not exists `_vt`": { 335 Fields: []*querypb.Field{{ 336 Type: sqltypes.Uint64, 337 }}, 338 Rows: [][]sqltypes.Value{}, 339 }, 340 "drop table if exists `_vt`.redo_log_transaction": { 341 Fields: []*querypb.Field{{ 342 Type: sqltypes.Uint64, 343 }}, 344 Rows: [][]sqltypes.Value{}, 345 }, 346 "drop table if exists `_vt`.redo_log_statement": { 347 Fields: []*querypb.Field{{ 348 Type: sqltypes.Uint64, 349 }}, 350 Rows: [][]sqltypes.Value{}, 351 }, 352 "drop table if exists `_vt`.transaction": { 353 Fields: []*querypb.Field{{ 354 Type: sqltypes.Uint64, 355 }}, 356 Rows: [][]sqltypes.Value{}, 357 }, 358 "drop table if exists `_vt`.participant": { 359 Fields: []*querypb.Field{{ 360 Type: sqltypes.Uint64, 361 }}, 362 Rows: [][]sqltypes.Value{}, 363 }, 364 "create table if not exists `_vt`.redo_state(\n dtid varbinary(512),\n state bigint,\n time_created bigint,\n primary key(dtid)\n\t) engine=InnoDB": { 365 Fields: []*querypb.Field{{ 366 Type: sqltypes.Uint64, 367 }}, 368 Rows: [][]sqltypes.Value{}, 369 }, 370 "create table if not exists `_vt`.redo_statement(\n dtid varbinary(512),\n id bigint,\n statement mediumblob,\n primary key(dtid, id)\n\t) engine=InnoDB": { 371 Fields: []*querypb.Field{{ 372 Type: sqltypes.Uint64, 373 }}, 374 Rows: [][]sqltypes.Value{}, 375 }, 376 "create table if not exists `_vt`.dt_state(\n dtid varbinary(512),\n state bigint,\n time_created bigint,\n primary key(dtid)\n\t) engine=InnoDB": { 377 Fields: []*querypb.Field{{ 378 Type: sqltypes.Uint64, 379 }}, 380 Rows: [][]sqltypes.Value{}, 381 }, 382 "create table if not exists `_vt`.dt_participant(\n dtid varbinary(512),\n\tid bigint,\n\tkeyspace varchar(256),\n\tshard varchar(256),\n primary key(dtid, id)\n\t) engine=InnoDB": { 383 384 Fields: []*querypb.Field{{ 385 Type: sqltypes.Uint64, 386 }}, 387 Rows: [][]sqltypes.Value{}, 388 }, 389 mysql.ShowRowsRead: sqltypes.MakeTestResult( 390 sqltypes.MakeTestFields( 391 "Variable_name|value", 392 "varchar|uint64", 393 ), 394 "Innodb_rows|0", 395 ), 396 } 397 398 for query, result := range schemaQueries { 399 tEnv.addResult(query, result) 400 } 401 402 showTableRows := make([][]sqltypes.Value, 0, 4) 403 for _, ddl := range ddls { 404 table := ddl.GetTable().Name.String() 405 options := "" 406 spec := ddl.GetTableSpec() 407 if spec != nil { 408 for _, option := range spec.Options { 409 if option.Name == "comment" && string(option.Value.Val) == "vitess_sequence" { 410 options = "vitess_sequence" 411 } 412 } 413 } 414 showTableRows = append(showTableRows, mysql.BaseShowTablesRow(table, false, options)) 415 } 416 tEnv.addResult(mysql.TablesWithSize57, &sqltypes.Result{ 417 Fields: mysql.BaseShowTablesFields, 418 Rows: showTableRows, 419 }) 420 tEnv.addResult(mysql.TablesWithSize80, &sqltypes.Result{ 421 Fields: mysql.BaseShowTablesFields, 422 Rows: showTableRows, 423 }) 424 425 indexRows := make([][]sqltypes.Value, 0, 4) 426 for _, ddl := range ddls { 427 table := sqlparser.String(ddl.GetTable().Name) 428 backtickedTable := sqlescape.EscapeID(sqlescape.UnescapeID(table)) 429 if ddl.GetOptLike() != nil { 430 likeTable := ddl.GetOptLike().LikeTable.Name.String() 431 backtickedLikeTable := sqlescape.EscapeID(sqlescape.UnescapeID(likeTable)) 432 433 likeQuery := "SELECT * FROM " + backtickedLikeTable + " WHERE 1 != 1" 434 query := "SELECT * FROM " + backtickedTable + " WHERE 1 != 1" 435 if tEnv.getResult(likeQuery) == nil { 436 return nil, fmt.Errorf("check your schema, table[%s] doesn't exist", likeTable) 437 } 438 tEnv.addResult(query, tEnv.getResult(likeQuery)) 439 440 likeQuery = fmt.Sprintf(mysqlctl.GetColumnNamesQuery, "database()", sqlescape.UnescapeID(likeTable)) 441 query = fmt.Sprintf(mysqlctl.GetColumnNamesQuery, "database()", sqlescape.UnescapeID(table)) 442 if tEnv.getResult(likeQuery) == nil { 443 return nil, fmt.Errorf("check your schema, table[%s] doesn't exist", likeTable) 444 } 445 tEnv.addResult(query, tEnv.getResult(likeQuery)) 446 continue 447 } 448 for _, idx := range ddl.GetTableSpec().Indexes { 449 if !idx.Info.Primary { 450 continue 451 } 452 for _, col := range idx.Columns { 453 row := mysql.ShowPrimaryRow(table, col.Column.String()) 454 indexRows = append(indexRows, row) 455 } 456 } 457 458 tEnv.tableColumns[table] = make(map[string]querypb.Type) 459 var rowTypes []*querypb.Field 460 var colTypes []*querypb.Field 461 var colValues [][]sqltypes.Value 462 colType := &querypb.Field{ 463 Name: "column_type", 464 Type: sqltypes.VarChar, 465 } 466 colTypes = append(colTypes, colType) 467 for _, col := range ddl.GetTableSpec().Columns { 468 colName := strings.ToLower(col.Name.String()) 469 rowType := &querypb.Field{ 470 Name: colName, 471 Type: col.Type.SQLType(), 472 } 473 rowTypes = append(rowTypes, rowType) 474 tEnv.tableColumns[table][colName] = col.Type.SQLType() 475 colValues = append(colValues, []sqltypes.Value{sqltypes.NewVarChar(colName)}) 476 } 477 tEnv.addResult("SELECT * FROM "+backtickedTable+" WHERE 1 != 1", &sqltypes.Result{ 478 Fields: rowTypes, 479 }) 480 query := fmt.Sprintf(mysqlctl.GetColumnNamesQuery, "database()", sqlescape.UnescapeID(table)) 481 tEnv.addResult(query, &sqltypes.Result{ 482 Fields: colTypes, 483 Rows: colValues, 484 }) 485 } 486 487 tEnv.addResult(mysql.BaseShowPrimary, &sqltypes.Result{ 488 Fields: mysql.ShowPrimaryFields, 489 Rows: indexRows, 490 }) 491 492 return tEnv, nil 493 } 494 495 // HandleQuery implements the fakesqldb query handler interface 496 func (t *explainTablet) HandleQuery(c *mysql.Conn, query string, callback func(*sqltypes.Result) error) error { 497 t.mu.Lock() 498 defer t.mu.Unlock() 499 500 if !strings.Contains(query, "1 != 1") { 501 t.mysqlQueries = append(t.mysqlQueries, &MysqlQuery{ 502 Time: t.currentTime, 503 SQL: query, 504 }) 505 } 506 507 // return the pre-computed results for any schema introspection queries 508 tEnv := t.vte.getGlobalTabletEnv() 509 result := tEnv.getResult(query) 510 emptyResult := &sqltypes.Result{} 511 if sidecardb.MatchesInitQuery(query) { 512 return callback(emptyResult) 513 } 514 if result != nil { 515 return callback(result) 516 } 517 switch sqlparser.Preview(query) { 518 case sqlparser.StmtSelect: 519 var err error 520 result, err = t.handleSelect(query) 521 if err != nil { 522 return err 523 } 524 case sqlparser.StmtBegin, sqlparser.StmtCommit, sqlparser.StmtSet, 525 sqlparser.StmtSavepoint, sqlparser.StmtSRollback, sqlparser.StmtRelease: 526 result = &sqltypes.Result{} 527 case sqlparser.StmtShow: 528 result = &sqltypes.Result{Fields: sqltypes.MakeTestFields("", "")} 529 case sqlparser.StmtInsert, sqlparser.StmtReplace, sqlparser.StmtUpdate, sqlparser.StmtDelete: 530 result = &sqltypes.Result{ 531 RowsAffected: 1, 532 } 533 default: 534 return fmt.Errorf("unsupported query %s", query) 535 } 536 537 return callback(result) 538 } 539 540 func (t *explainTablet) handleSelect(query string) (*sqltypes.Result, error) { 541 // Parse the select statement to figure out the table and columns 542 // that were referenced so that the synthetic response has the 543 // expected field names and types. 544 stmt, err := sqlparser.Parse(query) 545 if err != nil { 546 return nil, err 547 } 548 549 var selStmt *sqlparser.Select 550 switch stmt := stmt.(type) { 551 case *sqlparser.Select: 552 selStmt = stmt 553 case *sqlparser.Union: 554 selStmt = sqlparser.GetFirstSelect(stmt) 555 default: 556 return nil, fmt.Errorf("vtexplain: unsupported statement type +%v", reflect.TypeOf(stmt)) 557 } 558 559 // Gen4 supports more complex queries so we now need to 560 // handle multiple FROM clauses 561 tables := make([]*sqlparser.AliasedTableExpr, len(selStmt.From)) 562 for _, from := range selStmt.From { 563 tables = append(tables, getTables(from)...) 564 } 565 566 tableColumnMap := map[sqlparser.IdentifierCS]map[string]querypb.Type{} 567 for _, table := range tables { 568 if table == nil { 569 continue 570 } 571 572 tableName := sqlparser.String(sqlparser.GetTableName(table.Expr)) 573 columns, exists := t.vte.getGlobalTabletEnv().tableColumns[tableName] 574 if !exists && tableName != "" && tableName != "dual" { 575 return nil, fmt.Errorf("unable to resolve table name %s", tableName) 576 } 577 578 colTypeMap := map[string]querypb.Type{} 579 580 if table.As.IsEmpty() { 581 tableColumnMap[sqlparser.GetTableName(table.Expr)] = colTypeMap 582 } else { 583 tableColumnMap[table.As] = colTypeMap 584 } 585 586 for k, v := range columns { 587 if colType, exists := colTypeMap[k]; exists { 588 if colType != v { 589 return nil, fmt.Errorf("column type mismatch for column : %s, types: %d vs %d", k, colType, v) 590 } 591 continue 592 } 593 colTypeMap[k] = v 594 } 595 596 } 597 598 colNames, colTypes := t.analyzeExpressions(selStmt, tableColumnMap) 599 600 inColName, inVal, rowCount, s, err := t.analyzeWhere(selStmt, tableColumnMap) 601 if err != nil { 602 return s, err 603 } 604 605 fields := make([]*querypb.Field, len(colNames)) 606 rows := make([][]sqltypes.Value, 0, rowCount) 607 for i, col := range colNames { 608 colType := colTypes[i] 609 fields[i] = &querypb.Field{ 610 Name: col, 611 Type: colType, 612 } 613 } 614 615 for j := 0; j < rowCount; j++ { 616 values := make([]sqltypes.Value, len(colNames)) 617 for i, col := range colNames { 618 // Generate a fake value for the given column. For the column in the IN clause, 619 // use the provided values in the query, For numeric types, 620 // use the column index. For all other types, just shortcut to using 621 // a string type that encodes the column name + index. 622 colType := colTypes[i] 623 if len(inVal) > j && col == inColName { 624 values[i], _ = sqltypes.NewValue(querypb.Type_VARBINARY, inVal[j].Raw()) 625 } else if sqltypes.IsIntegral(colType) { 626 values[i] = sqltypes.NewInt32(int32(i + 1)) 627 } else if sqltypes.IsFloat(colType) { 628 values[i] = sqltypes.NewFloat64(1.0 + float64(i)) 629 } else { 630 values[i] = sqltypes.NewVarChar(fmt.Sprintf("%s_val_%d", col, i+1)) 631 } 632 } 633 rows = append(rows, values) 634 } 635 result := &sqltypes.Result{ 636 Fields: fields, 637 InsertID: 0, 638 Rows: rows, 639 } 640 641 resultJSON, _ := json.MarshalIndent(result, "", " ") 642 log.V(100).Infof("query %s result %s\n", query, string(resultJSON)) 643 return result, nil 644 } 645 646 func (t *explainTablet) analyzeWhere(selStmt *sqlparser.Select, tableColumnMap map[sqlparser.IdentifierCS]map[string]querypb.Type) (inColName string, inVal []sqltypes.Value, rowCount int, result *sqltypes.Result, err error) { 647 // the query against lookup table is in-query, handle it specifically 648 rowCount = 1 649 if selStmt.Where == nil { 650 return 651 } 652 v, ok := selStmt.Where.Expr.(*sqlparser.ComparisonExpr) 653 if !ok || v.Operator != sqlparser.InOp { 654 return 655 } 656 c, ok := v.Left.(*sqlparser.ColName) 657 if !ok { 658 return 659 } 660 colName := strings.ToLower(c.Name.String()) 661 colType := querypb.Type_VARCHAR 662 tableExpr := selStmt.From[0] 663 expr, ok := tableExpr.(*sqlparser.AliasedTableExpr) 664 if ok { 665 m := tableColumnMap[sqlparser.GetTableName(expr.Expr)] 666 if m != nil { 667 t, found := m[colName] 668 if found { 669 colType = t 670 } 671 } 672 } 673 674 values, ok := v.Right.(sqlparser.ValTuple) 675 if !ok { 676 return 677 } 678 for _, val := range values { 679 lit, ok := val.(*sqlparser.Literal) 680 if !ok { 681 continue 682 } 683 value, err := evalengine.LiteralToValue(lit) 684 if err != nil { 685 return "", nil, 0, nil, err 686 } 687 688 // Cast the value in the tuple to the expected value of the column 689 castedValue, err := evalengine.Cast(value, colType) 690 if err != nil { 691 return "", nil, 0, nil, err 692 } 693 694 // Check if we have a duplicate value 695 isNewValue := true 696 for _, v := range inVal { 697 result, err := evalengine.NullsafeCompare(v, value, collations.Default()) 698 if err != nil { 699 return "", nil, 0, nil, err 700 } 701 702 if result == 0 { 703 isNewValue = false 704 break 705 } 706 } 707 708 if isNewValue { 709 inVal = append(inVal, castedValue) 710 } 711 } 712 inColName = strings.ToLower(c.Name.String()) 713 return inColName, inVal, rowCount, nil, nil 714 } 715 716 func (t *explainTablet) analyzeExpressions(selStmt *sqlparser.Select, tableColumnMap map[sqlparser.IdentifierCS]map[string]querypb.Type) ([]string, []querypb.Type) { 717 colNames := make([]string, 0, 4) 718 colTypes := make([]querypb.Type, 0, 4) 719 for _, node := range selStmt.SelectExprs { 720 switch node := node.(type) { 721 case *sqlparser.AliasedExpr: 722 colNames, colTypes = inferColTypeFromExpr(node.Expr, tableColumnMap, colNames, colTypes) 723 case *sqlparser.StarExpr: 724 if node.TableName.Name.IsEmpty() { 725 // SELECT * 726 for _, colTypeMap := range tableColumnMap { 727 for col, colType := range colTypeMap { 728 colNames = append(colNames, col) 729 colTypes = append(colTypes, colType) 730 } 731 } 732 } else { 733 // SELECT tableName.* 734 colTypeMap := tableColumnMap[node.TableName.Name] 735 for col, colType := range colTypeMap { 736 colNames = append(colNames, col) 737 colTypes = append(colTypes, colType) 738 } 739 } 740 } 741 } 742 return colNames, colTypes 743 } 744 745 func getTables(node sqlparser.SQLNode) []*sqlparser.AliasedTableExpr { 746 var tables []*sqlparser.AliasedTableExpr 747 switch expr := node.(type) { 748 case *sqlparser.AliasedTableExpr: 749 tables = append(tables, expr) 750 case *sqlparser.JoinTableExpr: 751 tables = append(tables, getTables(expr.LeftExpr)...) 752 tables = append(tables, getTables(expr.RightExpr)...) 753 } 754 return tables 755 } 756 757 func inferColTypeFromExpr(node sqlparser.Expr, tableColumnMap map[sqlparser.IdentifierCS]map[string]querypb.Type, colNames []string, colTypes []querypb.Type) ([]string, []querypb.Type) { 758 switch node := node.(type) { 759 case *sqlparser.ColName: 760 if node.Qualifier.Name.IsEmpty() { 761 // Unqualified column name, try to search for it across all tables 762 col := strings.ToLower(node.Name.String()) 763 764 var colType querypb.Type 765 766 for _, colTypeMap := range tableColumnMap { 767 if colTypeMap[col] != querypb.Type_NULL_TYPE { 768 if colType != querypb.Type_NULL_TYPE { 769 log.Errorf("vtexplain: ambiguous column %s", col) 770 return colNames, colTypes 771 } 772 773 colType = colTypeMap[col] 774 } 775 } 776 777 if colType == querypb.Type_NULL_TYPE { 778 log.Errorf("vtexplain: invalid column %s.%s, tableColumnMap +%v", node.Qualifier.Name, col, tableColumnMap) 779 } 780 781 colNames = append(colNames, col) 782 colTypes = append(colTypes, colType) 783 } else { 784 // Qualified column name, try to look it up 785 colTypeMap := tableColumnMap[node.Qualifier.Name] 786 col := strings.ToLower(node.Name.String()) 787 colType := colTypeMap[col] 788 789 if colType == querypb.Type_NULL_TYPE { 790 log.Errorf("vtexplain: invalid column %s.%s, tableColumnMap +%v", node.Qualifier.Name, col, tableColumnMap) 791 } 792 793 colNames = append(colNames, col) 794 colTypes = append(colTypes, colType) 795 } 796 case sqlparser.Callable: 797 // As a shortcut, functions are integral types 798 colNames = append(colNames, sqlparser.String(node)) 799 colTypes = append(colTypes, querypb.Type_INT32) 800 case *sqlparser.Literal: 801 colNames = append(colNames, sqlparser.String(node)) 802 switch node.Type { 803 case sqlparser.IntVal: 804 fallthrough 805 case sqlparser.HexNum: 806 fallthrough 807 case sqlparser.HexVal: 808 fallthrough 809 case sqlparser.BitVal: 810 colTypes = append(colTypes, querypb.Type_INT32) 811 case sqlparser.StrVal: 812 colTypes = append(colTypes, querypb.Type_VARCHAR) 813 case sqlparser.FloatVal: 814 colTypes = append(colTypes, querypb.Type_FLOAT64) 815 case sqlparser.DecimalVal: 816 colTypes = append(colTypes, querypb.Type_DECIMAL) 817 default: 818 log.Errorf("vtexplain: unsupported sql value %s", sqlparser.String(node)) 819 } 820 case *sqlparser.CaseExpr: 821 colNames, colTypes = inferColTypeFromExpr(node.Whens[0].Val, tableColumnMap, colNames, colTypes) 822 case *sqlparser.NullVal: 823 colNames = append(colNames, sqlparser.String(node)) 824 colTypes = append(colTypes, querypb.Type_NULL_TYPE) 825 case *sqlparser.ComparisonExpr: 826 colNames = append(colNames, sqlparser.String(node)) 827 colTypes = append(colTypes, querypb.Type_INT64) 828 default: 829 log.Errorf("vtexplain: unsupported select expression type +%v node %s", reflect.TypeOf(node), sqlparser.String(node)) 830 } 831 832 return colNames, colTypes 833 }