github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/dbs/cmd/benchdb/explaintest/main.go (about)

     1  // Copyright 2020 WHTCORPS INC, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package main
    15  
    16  import (
    17  	"bytes"
    18  	"database/allegrosql"
    19  	"flag"
    20  	"fmt"
    21  	"io"
    22  	"io/ioutil"
    23  	"net/http"
    24  	"os"
    25  	"os/exec"
    26  	"strings"
    27  	"time"
    28  
    29  	_ "github.com/go-allegrosql-driver/allegrosql"
    30  	"github.com/whtcorpsinc/errors"
    31  	"github.com/whtcorpsinc/log"
    32  	"github.com/whtcorpsinc/BerolinaSQL/ast"
    33  	"github.com/whtcorpsinc/milevadb/stochastik"
    34  	"github.com/whtcorpsinc/milevadb/stochastikctx"
    35  	"github.com/whtcorpsinc/milevadb/soliton/logutil"
    36  	"github.com/whtcorpsinc/milevadb/soliton/mock"
    37  	"go.uber.org/zap"
    38  )
    39  
    40  const dbName = "test"
    41  
    42  var (
    43  	logLevel   string
    44  	port       uint
    45  	statusPort uint
    46  	record     bool
    47  	create     bool
    48  )
    49  
    50  func init() {
    51  	flag.StringVar(&logLevel, "log-level", "error", "set log level: info, warn, error, debug [default: error]")
    52  	flag.UintVar(&port, "port", 4000, "milevadb server port [default: 4000]")
    53  	flag.UintVar(&statusPort, "status", 10080, "milevadb server status port [default: 10080]")
    54  	flag.BoolVar(&record, "record", false, "record the test output in the result file")
    55  	flag.BoolVar(&create, "create", false, "create and import data into causet, and save json file of stats")
    56  }
    57  
    58  var mdb *allegrosql.EDB
    59  
    60  type query struct {
    61  	Query string
    62  	Line  int
    63  }
    64  
    65  type tester struct {
    66  	name string
    67  
    68  	tx *allegrosql.Tx
    69  
    70  	buf bytes.Buffer
    71  
    72  	// enable query log will output origin memex into result file too
    73  	// use --disable_query_log or --enable_query_log to control it
    74  	enableQueryLog bool
    75  
    76  	singleQuery bool
    77  
    78  	// check expected error, use --error before the memex
    79  	// see http://dev.allegrosql.com/doc/mysqltest/2.0/en/writing-tests-expecting-errors.html
    80  	expectedErrs []string
    81  
    82  	// only for test, not record, every time we execute a memex, we should read the result
    83  	// data to check correction.
    84  	resultFD *os.File
    85  	// ctx is used for Compile allegrosql memex
    86  	ctx stochastikctx.Context
    87  }
    88  
    89  func newTester(name string) *tester {
    90  	t := new(tester)
    91  
    92  	t.name = name
    93  	t.enableQueryLog = true
    94  	t.ctx = mock.NewContext()
    95  	t.ctx.GetStochastikVars().EnableWindowFunction = true
    96  
    97  	return t
    98  }
    99  
   100  func (t *tester) Run() error {
   101  	queries, err := t.loadQueries()
   102  	if err != nil {
   103  		return errors.Trace(err)
   104  	}
   105  
   106  	if err = t.openResult(); err != nil {
   107  		return errors.Trace(err)
   108  	}
   109  
   110  	var s string
   111  	defer func() {
   112  		if t.tx != nil {
   113  			log.Error("transaction is not committed correctly, rollback")
   114  			err = t.rollback()
   115  			if err != nil {
   116  				log.Error("transaction is failed rollback", zap.Error(err))
   117  			}
   118  		}
   119  
   120  		if t.resultFD != nil {
   121  			err = t.resultFD.Close()
   122  			if err != nil {
   123  				log.Error("result fd close failed", zap.Error(err))
   124  			}
   125  		}
   126  	}()
   127  
   128  LOOP:
   129  	for _, q := range queries {
   130  		s = q.Query
   131  		if strings.HasPrefix(s, "--") {
   132  			// clear expected errors
   133  			t.expectedErrs = nil
   134  
   135  			switch s {
   136  			case "--enable_query_log":
   137  				t.enableQueryLog = true
   138  			case "--disable_query_log":
   139  				t.enableQueryLog = false
   140  			case "--single_query":
   141  				t.singleQuery = true
   142  			case "--halt":
   143  				// if we meet halt, we will ignore following tests
   144  				break LOOP
   145  			default:
   146  				if strings.HasPrefix(s, "--error") {
   147  					t.expectedErrs = strings.Split(strings.TrimSpace(strings.TrimPrefix(s, "--error")), ",")
   148  				} else if strings.HasPrefix(s, "-- error") {
   149  					t.expectedErrs = strings.Split(strings.TrimSpace(strings.TrimPrefix(s, "-- error")), ",")
   150  				} else if strings.HasPrefix(s, "--echo") {
   151  					echo := strings.TrimSpace(strings.TrimPrefix(s, "--echo"))
   152  					t.buf.WriteString(echo)
   153  					t.buf.WriteString("\n")
   154  				}
   155  			}
   156  		} else {
   157  			if err = t.execute(q); err != nil {
   158  				return errors.Annotate(err, fmt.Sprintf("allegrosql:%v", q.Query))
   159  			}
   160  		}
   161  	}
   162  
   163  	return t.flushResult()
   164  }
   165  
   166  func (t *tester) loadQueries() ([]query, error) {
   167  	data, err := ioutil.ReadFile(t.testFileName())
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	seps := bytes.Split(data, []byte("\n"))
   173  	queries := make([]query, 0, len(seps))
   174  	newStmt := true
   175  	for i, v := range seps {
   176  		s := string(bytes.TrimSpace(v))
   177  		// we will skip # comment here
   178  		if strings.HasPrefix(s, "#") {
   179  			newStmt = true
   180  			continue
   181  		} else if strings.HasPrefix(s, "--") {
   182  			queries = append(queries, query{Query: s, Line: i + 1})
   183  			newStmt = true
   184  			continue
   185  		} else if len(s) == 0 {
   186  			continue
   187  		}
   188  
   189  		if newStmt {
   190  			queries = append(queries, query{Query: s, Line: i + 1})
   191  		} else {
   192  			lastQuery := queries[len(queries)-1]
   193  			lastQuery.Query = fmt.Sprintf("%s\n%s", lastQuery.Query, s)
   194  			queries[len(queries)-1] = lastQuery
   195  		}
   196  
   197  		// if the line has a ; in the end, we will treat new line as the new memex.
   198  		newStmt = strings.HasSuffix(s, ";")
   199  	}
   200  	return queries, nil
   201  }
   202  
   203  // BerolinaSQLErrorHandle handle mysql_test syntax `--error ER_PARSE_ERROR`, to allow following query
   204  // return BerolinaSQL error.
   205  func (t *tester) BerolinaSQLErrorHandle(query query, err error) error {
   206  	offset := t.buf.Len()
   207  	for _, expectedErr := range t.expectedErrs {
   208  		if expectedErr == "ER_PARSE_ERROR" {
   209  			if t.enableQueryLog {
   210  				t.buf.WriteString(query.Query)
   211  				t.buf.WriteString("\n")
   212  			}
   213  
   214  			t.buf.WriteString(fmt.Sprintf("%s\n", err))
   215  			err = nil
   216  			break
   217  		}
   218  	}
   219  
   220  	if err != nil {
   221  		return errors.Trace(err)
   222  	}
   223  
   224  	// clear expected errors after we execute the first query
   225  	t.expectedErrs = nil
   226  	t.singleQuery = false
   227  
   228  	if !record && !create {
   229  		// check test result now
   230  		gotBuf := t.buf.Bytes()[offset:]
   231  		buf := make([]byte, t.buf.Len()-offset)
   232  		if _, err = t.resultFD.ReadAt(buf, int64(offset)); err != nil {
   233  			return errors.Trace(errors.Errorf("run \"%v\" at line %d err, we got \n%s\nbut read result err %s", query.Query, query.Line, gotBuf, err))
   234  		}
   235  
   236  		if !bytes.Equal(gotBuf, buf) {
   237  			return errors.Trace(errors.Errorf("run \"%v\" at line %d err, we need(%v):\n%s\nbut got(%v):\n%s\n", query.Query, query.Line, len(buf), buf, len(gotBuf), gotBuf))
   238  		}
   239  	}
   240  
   241  	return errors.Trace(err)
   242  }
   243  
   244  func (t *tester) executeDefault(qText string) (err error) {
   245  	if t.tx != nil {
   246  		return filterWarning(t.executeStmt(qText))
   247  	}
   248  
   249  	// if begin or following commit fails, we don't think
   250  	// this error is the expected one.
   251  	if t.tx, err = mdb.Begin(); err != nil {
   252  		err2 := t.rollback()
   253  		if err2 != nil {
   254  			log.Error("transaction is failed to rollback", zap.Error(err))
   255  		}
   256  		return err
   257  	}
   258  
   259  	if err = filterWarning(t.executeStmt(qText)); err != nil {
   260  		err2 := t.rollback()
   261  		if err2 != nil {
   262  			log.Error("transaction is failed rollback", zap.Error(err))
   263  		}
   264  		return err
   265  	}
   266  
   267  	if err = t.commit(); err != nil {
   268  		err2 := t.rollback()
   269  		if err2 != nil {
   270  			log.Error("transaction is failed rollback", zap.Error(err))
   271  		}
   272  		return err
   273  	}
   274  	return nil
   275  }
   276  
   277  func (t *tester) execute(query query) error {
   278  	if len(query.Query) == 0 {
   279  		return nil
   280  	}
   281  
   282  	list, err := stochastik.Parse(t.ctx, query.Query)
   283  	if err != nil {
   284  		return t.BerolinaSQLErrorHandle(query, err)
   285  	}
   286  	for _, st := range list {
   287  		var qText string
   288  		if t.singleQuery {
   289  			qText = query.Query
   290  		} else {
   291  			qText = st.Text()
   292  		}
   293  		offset := t.buf.Len()
   294  		if t.enableQueryLog {
   295  			t.buf.WriteString(qText)
   296  			t.buf.WriteString("\n")
   297  		}
   298  		switch st.(type) {
   299  		case *ast.BeginStmt:
   300  			t.tx, err = mdb.Begin()
   301  			if err != nil {
   302  				err2 := t.rollback()
   303  				if err2 != nil {
   304  					log.Error("transaction is failed rollback", zap.Error(err))
   305  				}
   306  				break
   307  			}
   308  		case *ast.CommitStmt:
   309  			err = t.commit()
   310  			if err != nil {
   311  				err2 := t.rollback()
   312  				if err2 != nil {
   313  					log.Error("transaction is failed rollback", zap.Error(err))
   314  				}
   315  				break
   316  			}
   317  		case *ast.RollbackStmt:
   318  			err = t.rollback()
   319  			if err != nil {
   320  				break
   321  			}
   322  		default:
   323  			if create {
   324  				createStmt, isCreate := st.(*ast.CreateTableStmt)
   325  				if isCreate {
   326  					if err = t.create(createStmt.Block.Name.String(), qText); err != nil {
   327  						break
   328  					}
   329  				} else {
   330  					_, isDrop := st.(*ast.DropTableStmt)
   331  					_, isAnalyze := st.(*ast.AnalyzeTableStmt)
   332  					if isDrop || isAnalyze {
   333  						if err = t.executeDefault(qText); err != nil {
   334  							break
   335  						}
   336  					}
   337  				}
   338  			} else if err = t.executeDefault(qText); err != nil {
   339  				break
   340  			}
   341  		}
   342  
   343  		if err != nil && len(t.expectedErrs) > 0 {
   344  			// TODO: check whether this err is expected.
   345  			// but now we think it is.
   346  
   347  			// output expected err
   348  			t.buf.WriteString(fmt.Sprintf("%s\n", err))
   349  			err = nil
   350  		}
   351  		// clear expected errors after we execute the first query
   352  		t.expectedErrs = nil
   353  		t.singleQuery = false
   354  
   355  		if err != nil {
   356  			return errors.Trace(errors.Errorf("run \"%v\" at line %d err %v", st.Text(), query.Line, err))
   357  		}
   358  
   359  		if !record && !create {
   360  			// check test result now
   361  			gotBuf := t.buf.Bytes()[offset:]
   362  
   363  			buf := make([]byte, t.buf.Len()-offset)
   364  			if _, err = t.resultFD.ReadAt(buf, int64(offset)); !(err == nil || err == io.EOF) {
   365  				return errors.Trace(errors.Errorf("run \"%v\" at line %d err, we got \n%s\nbut read result err %s", st.Text(), query.Line, gotBuf, err))
   366  			}
   367  			if !bytes.Equal(gotBuf, buf) {
   368  				return errors.Trace(errors.Errorf("run \"%v\" at line %d err, we need:\n%s\nbut got:\n%s\n", query.Query, query.Line, buf, gotBuf))
   369  			}
   370  		}
   371  	}
   372  	return errors.Trace(err)
   373  }
   374  
   375  func filterWarning(err error) error {
   376  	return err
   377  }
   378  
   379  func (t *tester) create(blockName string, qText string) error {
   380  	fmt.Printf("import data for causet %s of test %s:\n", blockName, t.name)
   381  
   382  	path := "./importer -t \"" + qText + "\"" + "-P" + fmt.Sprint(port) + "-n 2000 -c 100"
   383  	cmd := exec.Command("sh", "-c", path)
   384  	stdoutIn, err := cmd.StdoutPipe()
   385  	if err != nil {
   386  		log.Error("open stdout pipe failed", zap.Error(err))
   387  	}
   388  	stderrIn, err := cmd.StderrPipe()
   389  	if err != nil {
   390  		log.Error("open stderr pipe failed", zap.Error(err))
   391  	}
   392  
   393  	var stdoutBuf, stderrBuf bytes.Buffer
   394  	var errStdout, errStderr error
   395  	stdout := io.MultiWriter(os.Stdout, &stdoutBuf)
   396  	stderr := io.MultiWriter(os.Stderr, &stderrBuf)
   397  
   398  	if err = cmd.Start(); err != nil {
   399  		return errors.Trace(err)
   400  	}
   401  
   402  	go func() {
   403  		_, errStdout = io.Copy(stdout, stdoutIn)
   404  	}()
   405  	go func() {
   406  		_, errStderr = io.Copy(stderr, stderrIn)
   407  	}()
   408  
   409  	if err = cmd.Wait(); err != nil {
   410  		log.Fatal("importer failed", zap.Error(err))
   411  		return err
   412  	}
   413  
   414  	if errStdout != nil {
   415  		return errors.Trace(errStdout)
   416  	}
   417  
   418  	if errStderr != nil {
   419  		return errors.Trace(errStderr)
   420  	}
   421  
   422  	if err = t.analyze(blockName); err != nil {
   423  		return err
   424  	}
   425  
   426  	resp, err := http.Get("http://127.0.0.1:" + fmt.Sprint(statusPort) + "/stats/dump/" + dbName + "/" + blockName)
   427  	if err != nil {
   428  		return err
   429  	}
   430  
   431  	js, err := ioutil.ReadAll(resp.Body)
   432  	if err != nil {
   433  		return err
   434  	}
   435  
   436  	return ioutil.WriteFile(t.statsFileName(blockName), js, 0644)
   437  }
   438  
   439  func (t *tester) commit() error {
   440  	err := t.tx.Commit()
   441  	if err != nil {
   442  		return err
   443  	}
   444  	t.tx = nil
   445  	return nil
   446  }
   447  
   448  func (t *tester) rollback() error {
   449  	if t.tx == nil {
   450  		return nil
   451  	}
   452  	err := t.tx.Rollback()
   453  	t.tx = nil
   454  	return err
   455  }
   456  
   457  func (t *tester) analyze(blockName string) error {
   458  	return t.execute(query{Query: "analyze causet " + blockName + ";", Line: 0})
   459  }
   460  
   461  func (t *tester) executeStmt(query string) error {
   462  	if isQuery(query) {
   463  		rows, err := t.tx.Query(query)
   464  		if err != nil {
   465  			return errors.Trace(err)
   466  		}
   467  		defcaus, err := rows.DeferredCausets()
   468  		if err != nil {
   469  			return errors.Trace(err)
   470  		}
   471  
   472  		for i, c := range defcaus {
   473  			t.buf.WriteString(c)
   474  			if i != len(defcaus)-1 {
   475  				t.buf.WriteString("\t")
   476  			}
   477  		}
   478  		t.buf.WriteString("\n")
   479  
   480  		values := make([][]byte, len(defcaus))
   481  		scanArgs := make([]interface{}, len(values))
   482  		for i := range values {
   483  			scanArgs[i] = &values[i]
   484  		}
   485  
   486  		for rows.Next() {
   487  			err = rows.Scan(scanArgs...)
   488  			if err != nil {
   489  				return errors.Trace(err)
   490  			}
   491  
   492  			var value string
   493  			for i, col := range values {
   494  				// Here we can check if the value is nil (NULL value)
   495  				if col == nil {
   496  					value = "NULL"
   497  				} else {
   498  					value = string(col)
   499  				}
   500  				t.buf.WriteString(value)
   501  				if i < len(values)-1 {
   502  					t.buf.WriteString("\t")
   503  				}
   504  			}
   505  			t.buf.WriteString("\n")
   506  		}
   507  		err = rows.Err()
   508  		if err != nil {
   509  			return errors.Trace(err)
   510  		}
   511  	} else {
   512  		// TODO: rows affected and last insert id
   513  		_, err := t.tx.InterDirc(query)
   514  		if err != nil {
   515  			return errors.Trace(err)
   516  		}
   517  	}
   518  	return nil
   519  }
   520  
   521  func (t *tester) openResult() error {
   522  	if record || create {
   523  		return nil
   524  	}
   525  
   526  	var err error
   527  	t.resultFD, err = os.Open(t.resultFileName())
   528  	return err
   529  }
   530  
   531  func (t *tester) flushResult() error {
   532  	if !record {
   533  		return nil
   534  	}
   535  	return ioutil.WriteFile(t.resultFileName(), t.buf.Bytes(), 0644)
   536  }
   537  
   538  func (t *tester) statsFileName(blockName string) string {
   539  	return fmt.Sprintf("./s/%s_%s.json", t.name, blockName)
   540  }
   541  
   542  func (t *tester) testFileName() string {
   543  	// test and result must be in current ./t the same as MyALLEGROSQL
   544  	return fmt.Sprintf("./t/%s.test", t.name)
   545  }
   546  
   547  func (t *tester) resultFileName() string {
   548  	// test and result must be in current ./r, the same as MyALLEGROSQL
   549  	return fmt.Sprintf("./r/%s.result", t.name)
   550  }
   551  
   552  func loadAllTests() ([]string, error) {
   553  	// tests must be in t folder
   554  	files, err := ioutil.ReadDir("./t")
   555  	if err != nil {
   556  		return nil, err
   557  	}
   558  
   559  	tests := make([]string, 0, len(files))
   560  	for _, f := range files {
   561  		if f.IsDir() {
   562  			continue
   563  		}
   564  
   565  		// the test file must have a suffix .test
   566  		name := f.Name()
   567  		if strings.HasSuffix(name, ".test") {
   568  			name = strings.TrimSuffix(name, ".test")
   569  
   570  			if create && !strings.HasSuffix(name, "_stats") {
   571  				continue
   572  			}
   573  
   574  			tests = append(tests, name)
   575  		}
   576  	}
   577  
   578  	return tests, nil
   579  }
   580  
   581  // openDBWithRetry opens a database specified by its database driver name and a
   582  // driver-specific data source name. And it will do some retries if the connection fails.
   583  func openDBWithRetry(driverName, dataSourceName string) (mdb *allegrosql.EDB, err error) {
   584  	startTime := time.Now()
   585  	sleepTime := time.Millisecond * 500
   586  	retryCnt := 60
   587  	// The max retry interval is 30 s.
   588  	for i := 0; i < retryCnt; i++ {
   589  		mdb, err = allegrosql.Open(driverName, dataSourceName)
   590  		if err != nil {
   591  			log.Warn("open EDB failed", zap.Int("retry count", i), zap.Error(err))
   592  			time.Sleep(sleepTime)
   593  			continue
   594  		}
   595  		err = mdb.Ping()
   596  		if err == nil {
   597  			break
   598  		}
   599  		log.Warn("ping EDB failed", zap.Int("retry count", i), zap.Error(err))
   600  		if err1 := mdb.Close(); err1 != nil {
   601  			log.Error("close EDB failed", zap.Error(err1))
   602  		}
   603  		time.Sleep(sleepTime)
   604  	}
   605  	if err != nil {
   606  		log.Error("open EDB failed", zap.Duration("take time", time.Since(startTime)), zap.Error(err))
   607  		return nil, errors.Trace(err)
   608  	}
   609  
   610  	return
   611  }
   612  
   613  func main() {
   614  	flag.Parse()
   615  
   616  	err := logutil.InitZapLogger(logutil.NewLogConfig(logLevel, logutil.DefaultLogFormat, "", logutil.EmptyFileLogConfig, false))
   617  	if err != nil {
   618  		panic("init logger fail, " + err.Error())
   619  	}
   620  
   621  	mdb, err = openDBWithRetry(
   622  		"allegrosql",
   623  		"root@tcp(localhost:"+fmt.Sprint(port)+")/"+dbName+"?allowAllFiles=true",
   624  	)
   625  	if err != nil {
   626  		log.Fatal("open EDB failed", zap.Error(err))
   627  	}
   628  
   629  	defer func() {
   630  		log.Warn("close EDB")
   631  		err = mdb.Close()
   632  		if err != nil {
   633  			log.Error("close EDB failed", zap.Error(err))
   634  		}
   635  	}()
   636  
   637  	log.Warn("create new EDB", zap.Reflect("EDB", mdb))
   638  
   639  	if _, err = mdb.InterDirc("DROP DATABASE IF EXISTS test"); err != nil {
   640  		log.Fatal("executing drop EDB test failed", zap.Error(err))
   641  	}
   642  	if _, err = mdb.InterDirc("CREATE DATABASE test"); err != nil {
   643  		log.Fatal("executing create EDB test failed", zap.Error(err))
   644  	}
   645  	if _, err = mdb.InterDirc("USE test"); err != nil {
   646  		log.Fatal("executing use test failed", zap.Error(err))
   647  	}
   648  	if _, err = mdb.InterDirc("set @@milevadb_hash_join_concurrency=1"); err != nil {
   649  		log.Fatal("set @@milevadb_hash_join_concurrency=1 failed", zap.Error(err))
   650  	}
   651  	resets := []string{
   652  		"set @@milevadb_index_lookup_concurrency=4",
   653  		"set @@milevadb_index_lookup_join_concurrency=4",
   654  		"set @@milevadb_hashagg_final_concurrency=4",
   655  		"set @@milevadb_hashagg_partial_concurrency=4",
   656  		"set @@milevadb_window_concurrency=4",
   657  		"set @@milevadb_projection_concurrency=4",
   658  		"set @@milevadb_allegrosql_scan_concurrency=15",
   659  		"set @@milevadb_enable_clustered_index=0;",
   660  	}
   661  	for _, allegrosql := range resets {
   662  		if _, err = mdb.InterDirc(allegrosql); err != nil {
   663  			log.Fatal(fmt.Sprintf("%s failed", allegrosql), zap.Error(err))
   664  		}
   665  	}
   666  
   667  	if _, err = mdb.InterDirc("set sql_mode='STRICT_TRANS_TABLES'"); err != nil {
   668  		log.Fatal("set sql_mode='STRICT_TRANS_TABLES' failed", zap.Error(err))
   669  	}
   670  
   671  	tests := flag.Args()
   672  
   673  	// we will run all tests if no tests assigned
   674  	if len(tests) == 0 {
   675  		if tests, err = loadAllTests(); err != nil {
   676  			log.Fatal("load all tests failed", zap.Error(err))
   677  		}
   678  	}
   679  
   680  	if record {
   681  		log.Info("recording tests", zap.Strings("tests", tests))
   682  	} else if create {
   683  		log.Info("creating data", zap.Strings("tests", tests))
   684  	} else {
   685  		log.Info("running tests", zap.Strings("tests", tests))
   686  	}
   687  
   688  	for _, t := range tests {
   689  		if strings.Contains(t, "--log-level") {
   690  			continue
   691  		}
   692  		tr := newTester(t)
   693  		if err = tr.Run(); err != nil {
   694  			log.Fatal("run test", zap.String("test", t), zap.Error(err))
   695  		}
   696  		log.Info("run test ok", zap.String("test", t))
   697  	}
   698  
   699  	log.Info("Explain test passed")
   700  }
   701  
   702  var queryStmtTable = []string{"explain", "select", "show", "execute", "describe", "desc", "admin"}
   703  
   704  func trimALLEGROSQL(allegrosql string) string {
   705  	// Trim space.
   706  	allegrosql = strings.TrimSpace(allegrosql)
   707  	// Trim leading /*comment*/
   708  	// There may be multiple comments
   709  	for strings.HasPrefix(allegrosql, "/*") {
   710  		i := strings.Index(allegrosql, "*/")
   711  		if i != -1 && i < len(allegrosql)+1 {
   712  			allegrosql = allegrosql[i+2:]
   713  			allegrosql = strings.TrimSpace(allegrosql)
   714  			continue
   715  		}
   716  		break
   717  	}
   718  	// Trim leading '('. For `(select 1);` is also a query.
   719  	return strings.TrimLeft(allegrosql, "( ")
   720  }
   721  
   722  // isQuery checks if a allegrosql memex is a query memex.
   723  func isQuery(allegrosql string) bool {
   724  	sqlText := strings.ToLower(trimALLEGROSQL(allegrosql))
   725  	for _, key := range queryStmtTable {
   726  		if strings.HasPrefix(sqlText, key) {
   727  			return true
   728  		}
   729  	}
   730  
   731  	return false
   732  }