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  }