github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/allegrosql/memtable_predicate_extractor.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 embedded
    15  
    16  import (
    17  	"bytes"
    18  	"fmt"
    19  	"math"
    20  	"regexp"
    21  	"sort"
    22  	"strconv"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/cznic/mathutil"
    27  	"github.com/whtcorpsinc/BerolinaSQL/allegrosql"
    28  	"github.com/whtcorpsinc/BerolinaSQL/ast"
    29  	"github.com/whtcorpsinc/fidelpb/go-fidelpb"
    30  	"github.com/whtcorpsinc/milevadb/memex"
    31  	"github.com/whtcorpsinc/milevadb/schemareplicant"
    32  	"github.com/whtcorpsinc/milevadb/soliton/set"
    33  	"github.com/whtcorpsinc/milevadb/soliton/stringutil"
    34  	"github.com/whtcorpsinc/milevadb/stochastikctx"
    35  	"github.com/whtcorpsinc/milevadb/types"
    36  )
    37  
    38  // MemBlockPredicateExtractor is used to extract some predicates from `WHERE` clause
    39  // and push the predicates down to the data retrieving on reading memory causet stage.
    40  //
    41  // e.g:
    42  // SELECT * FROM cluster_config WHERE type='einsteindb' AND instance='192.168.1.9:2379'
    43  // We must request all components in the cluster via HTTP API for retrieving
    44  // configurations and filter them by `type/instance` columns.
    45  //
    46  // The purpose of defining a `MemBlockPredicateExtractor` is to optimize this
    47  // 1. Define a `ClusterConfigBlockPredicateExtractor`
    48  // 2. Extract the `type/instance` columns on the logic optimizing stage and save them via fields.
    49  // 3. Passing the extractor to the `ClusterReaderInterDircInterDirc` interlock
    50  // 4. InterlockingDirectorate sends requests to the target components instead of all of the components
    51  type MemBlockPredicateExtractor interface {
    52  	// Extracts predicates which can be pushed down and returns the remained predicates
    53  	Extract(stochastikctx.Context, *memex.Schema, []*types.FieldName, []memex.Expression) (remained []memex.Expression)
    54  	explainInfo(p *PhysicalMemBlock) string
    55  }
    56  
    57  // extractHelper contains some common utililty functions for all extractor.
    58  // define an individual struct instead of a bunch of un-exported functions
    59  // to avoid polluting the global scope of current package.
    60  type extractHelper struct{}
    61  
    62  func (helper extractHelper) extractDefCausInConsExpr(extractDefCauss map[int64]*types.FieldName, expr *memex.ScalarFunction) (string, []types.Causet) {
    63  	args := expr.GetArgs()
    64  	col, isDefCaus := args[0].(*memex.DeferredCauset)
    65  	if !isDefCaus {
    66  		return "", nil
    67  	}
    68  	name, found := extractDefCauss[col.UniqueID]
    69  	if !found {
    70  		return "", nil
    71  	}
    72  	// All memexs in IN must be a constant
    73  	// SELECT * FROM t1 WHERE c IN ('1', '2')
    74  	var results []types.Causet
    75  	for _, arg := range args[1:] {
    76  		constant, ok := arg.(*memex.Constant)
    77  		if !ok || constant.DeferredExpr != nil || constant.ParamMarker != nil {
    78  			return "", nil
    79  		}
    80  		results = append(results, constant.Value)
    81  	}
    82  	return name.DefCausName.L, results
    83  }
    84  
    85  func (helper extractHelper) extractDefCausBinaryOpConsExpr(extractDefCauss map[int64]*types.FieldName, expr *memex.ScalarFunction) (string, []types.Causet) {
    86  	args := expr.GetArgs()
    87  	var col *memex.DeferredCauset
    88  	var colIdx int
    89  	// c = 'rhs'
    90  	// 'lhs' = c
    91  	for i := 0; i < 2; i++ {
    92  		var isDefCaus bool
    93  		col, isDefCaus = args[i].(*memex.DeferredCauset)
    94  		if isDefCaus {
    95  			colIdx = i
    96  			break
    97  		}
    98  	}
    99  	if col == nil {
   100  		return "", nil
   101  	}
   102  
   103  	name, found := extractDefCauss[col.UniqueID]
   104  	if !found {
   105  		return "", nil
   106  	}
   107  	// The `lhs/rhs` of EQ memex must be a constant
   108  	// SELECT * FROM t1 WHERE c='rhs'
   109  	// SELECT * FROM t1 WHERE 'lhs'=c
   110  	constant, ok := args[1-colIdx].(*memex.Constant)
   111  	if !ok || constant.DeferredExpr != nil || constant.ParamMarker != nil {
   112  		return "", nil
   113  	}
   114  	return name.DefCausName.L, []types.Causet{constant.Value}
   115  }
   116  
   117  // extract the OR memex, e.g:
   118  // SELECT * FROM t1 WHERE c1='a' OR c1='b' OR c1='c'
   119  func (helper extractHelper) extractDefCausOrExpr(extractDefCauss map[int64]*types.FieldName, expr *memex.ScalarFunction) (string, []types.Causet) {
   120  	args := expr.GetArgs()
   121  	lhs, ok := args[0].(*memex.ScalarFunction)
   122  	if !ok {
   123  		return "", nil
   124  	}
   125  	rhs, ok := args[1].(*memex.ScalarFunction)
   126  	if !ok {
   127  		return "", nil
   128  	}
   129  	// Define an inner function to avoid populate the outer scope
   130  	var extract = func(extractDefCauss map[int64]*types.FieldName, fn *memex.ScalarFunction) (string, []types.Causet) {
   131  		switch fn.FuncName.L {
   132  		case ast.EQ:
   133  			return helper.extractDefCausBinaryOpConsExpr(extractDefCauss, fn)
   134  		case ast.LogicOr:
   135  			return helper.extractDefCausOrExpr(extractDefCauss, fn)
   136  		case ast.In:
   137  			return helper.extractDefCausInConsExpr(extractDefCauss, fn)
   138  		default:
   139  			return "", nil
   140  		}
   141  	}
   142  	lhsDefCausName, lhsCausets := extract(extractDefCauss, lhs)
   143  	if lhsDefCausName == "" {
   144  		return "", nil
   145  	}
   146  	rhsDefCausName, rhsCausets := extract(extractDefCauss, rhs)
   147  	if lhsDefCausName == rhsDefCausName {
   148  		return lhsDefCausName, append(lhsCausets, rhsCausets...)
   149  	}
   150  	return "", nil
   151  }
   152  
   153  // merges `lhs` and `datums` with CNF logic
   154  // 1. Returns `datums` set if the `lhs` is an empty set
   155  // 2. Returns the intersection of `datums` and `lhs` if the `lhs` is not an empty set
   156  func (helper extractHelper) merge(lhs set.StringSet, datums []types.Causet, toLower bool) set.StringSet {
   157  	tmpNodeTypes := set.NewStringSet()
   158  	for _, causet := range datums {
   159  		s, err := causet.ToString()
   160  		if err != nil {
   161  			return nil
   162  		}
   163  		if toLower {
   164  			s = strings.ToLower(s)
   165  		}
   166  		tmpNodeTypes.Insert(s)
   167  	}
   168  	if len(lhs) > 0 {
   169  		return lhs.Intersection(tmpNodeTypes)
   170  	}
   171  	return tmpNodeTypes
   172  }
   173  
   174  func (helper extractHelper) extractDefCaus(
   175  	schemaReplicant *memex.Schema,
   176  	names []*types.FieldName,
   177  	predicates []memex.Expression,
   178  	extractDefCausName string,
   179  	valueToLower bool,
   180  ) (
   181  	remained []memex.Expression,
   182  	skipRequest bool,
   183  	result set.StringSet,
   184  ) {
   185  	remained = make([]memex.Expression, 0, len(predicates))
   186  	result = set.NewStringSet()
   187  	extractDefCauss := helper.findDeferredCauset(schemaReplicant, names, extractDefCausName)
   188  	if len(extractDefCauss) == 0 {
   189  		return predicates, false, result
   190  	}
   191  
   192  	// We should use INTERSECTION of sets because of the predicates is CNF array
   193  	for _, expr := range predicates {
   194  		fn, ok := expr.(*memex.ScalarFunction)
   195  		if !ok {
   196  			continue
   197  		}
   198  		var colName string
   199  		var datums []types.Causet // the memory of datums should not be reused, they will be put into result.
   200  		switch fn.FuncName.L {
   201  		case ast.EQ:
   202  			colName, datums = helper.extractDefCausBinaryOpConsExpr(extractDefCauss, fn)
   203  		case ast.In:
   204  			colName, datums = helper.extractDefCausInConsExpr(extractDefCauss, fn)
   205  		case ast.LogicOr:
   206  			colName, datums = helper.extractDefCausOrExpr(extractDefCauss, fn)
   207  		}
   208  		if colName == extractDefCausName {
   209  			result = helper.merge(result, datums, valueToLower)
   210  			skipRequest = len(result) == 0
   211  		} else {
   212  			remained = append(remained, expr)
   213  		}
   214  		// There are no data if the low-level interlock skip request, so the filter can be droped
   215  		if skipRequest {
   216  			remained = remained[:0]
   217  			break
   218  		}
   219  	}
   220  	return
   221  }
   222  
   223  // extracts the string pattern column, e.g:
   224  // SELECT * FROM t WHERE c LIKE '%a%'
   225  // SELECT * FROM t WHERE c LIKE '%a%' AND c REGEXP '.*xxx.*'
   226  // SELECT * FROM t WHERE c LIKE '%a%' OR c REGEXP '.*xxx.*'
   227  func (helper extractHelper) extractLikePatternDefCaus(
   228  	schemaReplicant *memex.Schema,
   229  	names []*types.FieldName,
   230  	predicates []memex.Expression,
   231  	extractDefCausName string,
   232  ) (
   233  	remained []memex.Expression,
   234  	patterns []string,
   235  ) {
   236  	remained = make([]memex.Expression, 0, len(predicates))
   237  	extractDefCauss := helper.findDeferredCauset(schemaReplicant, names, extractDefCausName)
   238  	if len(extractDefCauss) == 0 {
   239  		return predicates, nil
   240  	}
   241  
   242  	// We use a string array to save multiple patterns because the Golang and Rust don't
   243  	// support perl-like CNF regular memex: (?=expr1)(?=expr2).
   244  	// e.g:
   245  	// SELECT * FROM t WHERE c LIKE '%a%' AND c LIKE '%b%' AND c REGEXP 'gc.*[0-9]{10,20}'
   246  	for _, expr := range predicates {
   247  		fn, ok := expr.(*memex.ScalarFunction)
   248  		if !ok {
   249  			remained = append(remained, expr)
   250  			continue
   251  		}
   252  
   253  		var canBuildPattern bool
   254  		var pattern string
   255  		// We use '|' to combine DNF regular memex: .*a.*|.*b.*
   256  		// e.g:
   257  		// SELECT * FROM t WHERE c LIKE '%a%' OR c LIKE '%b%'
   258  		if fn.FuncName.L == ast.LogicOr {
   259  			canBuildPattern, pattern = helper.extractOrLikePattern(fn, extractDefCausName, extractDefCauss)
   260  		} else {
   261  			canBuildPattern, pattern = helper.extractLikePattern(fn, extractDefCausName, extractDefCauss)
   262  		}
   263  		if canBuildPattern {
   264  			patterns = append(patterns, pattern)
   265  		} else {
   266  			remained = append(remained, expr)
   267  		}
   268  	}
   269  	return
   270  }
   271  
   272  func (helper extractHelper) extractOrLikePattern(
   273  	orFunc *memex.ScalarFunction,
   274  	extractDefCausName string,
   275  	extractDefCauss map[int64]*types.FieldName,
   276  ) (
   277  	ok bool,
   278  	pattern string,
   279  ) {
   280  	predicates := memex.SplitDNFItems(orFunc)
   281  	if len(predicates) == 0 {
   282  		return false, ""
   283  	}
   284  
   285  	patternBuilder := make([]string, 0, len(predicates))
   286  	for _, predicate := range predicates {
   287  		fn, ok := predicate.(*memex.ScalarFunction)
   288  		if !ok {
   289  			return false, ""
   290  		}
   291  
   292  		ok, partPattern := helper.extractLikePattern(fn, extractDefCausName, extractDefCauss)
   293  		if !ok {
   294  			return false, ""
   295  		}
   296  		patternBuilder = append(patternBuilder, partPattern)
   297  	}
   298  	return true, strings.Join(patternBuilder, "|")
   299  }
   300  
   301  func (helper extractHelper) extractLikePattern(
   302  	fn *memex.ScalarFunction,
   303  	extractDefCausName string,
   304  	extractDefCauss map[int64]*types.FieldName,
   305  ) (
   306  	ok bool,
   307  	pattern string,
   308  ) {
   309  	var colName string
   310  	var datums []types.Causet
   311  	switch fn.FuncName.L {
   312  	case ast.EQ, ast.Like, ast.Regexp:
   313  		colName, datums = helper.extractDefCausBinaryOpConsExpr(extractDefCauss, fn)
   314  	}
   315  	if colName == extractDefCausName {
   316  		switch fn.FuncName.L {
   317  		case ast.EQ:
   318  			return true, "^" + regexp.QuoteMeta(datums[0].GetString()) + "$"
   319  		case ast.Like:
   320  			return true, stringutil.CompileLike2Regexp(datums[0].GetString())
   321  		case ast.Regexp:
   322  			return true, datums[0].GetString()
   323  		default:
   324  			return false, ""
   325  		}
   326  	} else {
   327  		return false, ""
   328  	}
   329  }
   330  
   331  func (helper extractHelper) findDeferredCauset(schemaReplicant *memex.Schema, names []*types.FieldName, colName string) map[int64]*types.FieldName {
   332  	extractDefCauss := make(map[int64]*types.FieldName)
   333  	for i, name := range names {
   334  		if name.DefCausName.L == colName {
   335  			extractDefCauss[schemaReplicant.DeferredCausets[i].UniqueID] = name
   336  		}
   337  	}
   338  	return extractDefCauss
   339  }
   340  
   341  // getTimeFunctionName is used to get the (time) function name.
   342  // For the memex that push down to the interlock, the function name is different with normal compare function,
   343  // Then getTimeFunctionName will do a sample function name convert.
   344  // Currently, this is used to support query `CLUSTER_SLOW_QUERY` at any time.
   345  func (helper extractHelper) getTimeFunctionName(fn *memex.ScalarFunction) string {
   346  	switch fn.Function.PbCode() {
   347  	case fidelpb.ScalarFuncSig_GTTime:
   348  		return ast.GT
   349  	case fidelpb.ScalarFuncSig_GETime:
   350  		return ast.GE
   351  	case fidelpb.ScalarFuncSig_LTTime:
   352  		return ast.LT
   353  	case fidelpb.ScalarFuncSig_LETime:
   354  		return ast.LE
   355  	case fidelpb.ScalarFuncSig_EQTime:
   356  		return ast.EQ
   357  	default:
   358  		return fn.FuncName.L
   359  	}
   360  }
   361  
   362  // extracts the time range column, e.g:
   363  // SELECT * FROM t WHERE time='2020-10-10 10:10:10'
   364  // SELECT * FROM t WHERE time>'2020-10-10 10:10:10' AND time<'2020-10-11 10:10:10'
   365  func (helper extractHelper) extractTimeRange(
   366  	ctx stochastikctx.Context,
   367  	schemaReplicant *memex.Schema,
   368  	names []*types.FieldName,
   369  	predicates []memex.Expression,
   370  	extractDefCausName string,
   371  	timezone *time.Location,
   372  ) (
   373  	remained []memex.Expression,
   374  	// unix timestamp in nanoseconds
   375  	startTime int64,
   376  	endTime int64,
   377  ) {
   378  	remained = make([]memex.Expression, 0, len(predicates))
   379  	extractDefCauss := helper.findDeferredCauset(schemaReplicant, names, extractDefCausName)
   380  	if len(extractDefCauss) == 0 {
   381  		return predicates, startTime, endTime
   382  	}
   383  
   384  	for _, expr := range predicates {
   385  		fn, ok := expr.(*memex.ScalarFunction)
   386  		if !ok {
   387  			remained = append(remained, expr)
   388  			continue
   389  		}
   390  
   391  		var colName string
   392  		var datums []types.Causet
   393  		fnName := helper.getTimeFunctionName(fn)
   394  		switch fnName {
   395  		case ast.GT, ast.GE, ast.LT, ast.LE, ast.EQ:
   396  			colName, datums = helper.extractDefCausBinaryOpConsExpr(extractDefCauss, fn)
   397  		}
   398  
   399  		if colName == extractDefCausName {
   400  			timeType := types.NewFieldType(allegrosql.TypeDatetime)
   401  			timeType.Decimal = 6
   402  			timeCauset, err := datums[0].ConvertTo(ctx.GetStochastikVars().StmtCtx, timeType)
   403  			if err != nil || timeCauset.HoTT() == types.HoTTNull {
   404  				remained = append(remained, expr)
   405  				continue
   406  			}
   407  
   408  			mysqlTime := timeCauset.GetMysqlTime()
   409  			timestamp := time.Date(mysqlTime.Year(),
   410  				time.Month(mysqlTime.Month()),
   411  				mysqlTime.Day(),
   412  				mysqlTime.Hour(),
   413  				mysqlTime.Minute(),
   414  				mysqlTime.Second(),
   415  				mysqlTime.Microsecond()*1000,
   416  				timezone,
   417  			).UnixNano()
   418  
   419  			switch fnName {
   420  			case ast.EQ:
   421  				startTime = mathutil.MaxInt64(startTime, timestamp)
   422  				if endTime == 0 {
   423  					endTime = timestamp
   424  				} else {
   425  					endTime = mathutil.MinInt64(endTime, timestamp)
   426  				}
   427  			case ast.GT:
   428  				// FixMe: add 1ms is not absolutely correct here, just because the log search precision is millisecond.
   429  				startTime = mathutil.MaxInt64(startTime, timestamp+int64(time.Millisecond))
   430  			case ast.GE:
   431  				startTime = mathutil.MaxInt64(startTime, timestamp)
   432  			case ast.LT:
   433  				if endTime == 0 {
   434  					endTime = timestamp - int64(time.Millisecond)
   435  				} else {
   436  					endTime = mathutil.MinInt64(endTime, timestamp-int64(time.Millisecond))
   437  				}
   438  			case ast.LE:
   439  				if endTime == 0 {
   440  					endTime = timestamp
   441  				} else {
   442  					endTime = mathutil.MinInt64(endTime, timestamp)
   443  				}
   444  			default:
   445  				remained = append(remained, expr)
   446  			}
   447  		} else {
   448  			remained = append(remained, expr)
   449  		}
   450  	}
   451  	return
   452  }
   453  
   454  func (helper extractHelper) parseQuantiles(quantileSet set.StringSet) []float64 {
   455  	quantiles := make([]float64, 0, len(quantileSet))
   456  	for k := range quantileSet {
   457  		v, err := strconv.ParseFloat(k, 64)
   458  		if err != nil {
   459  			// ignore the parse error won't affect result.
   460  			continue
   461  		}
   462  		quantiles = append(quantiles, v)
   463  	}
   464  	sort.Float64s(quantiles)
   465  	return quantiles
   466  }
   467  
   468  func (helper extractHelper) extractDefCauss(
   469  	schemaReplicant *memex.Schema,
   470  	names []*types.FieldName,
   471  	predicates []memex.Expression,
   472  	excludeDefCauss set.StringSet,
   473  	valueToLower bool) ([]memex.Expression, bool, map[string]set.StringSet) {
   474  	defcaus := map[string]set.StringSet{}
   475  	remained := predicates
   476  	skipRequest := false
   477  	// Extract the label columns.
   478  	for _, name := range names {
   479  		if excludeDefCauss.Exist(name.DefCausName.L) {
   480  			continue
   481  		}
   482  		var values set.StringSet
   483  		remained, skipRequest, values = helper.extractDefCaus(schemaReplicant, names, remained, name.DefCausName.L, valueToLower)
   484  		if skipRequest {
   485  			return nil, true, nil
   486  		}
   487  		if len(values) == 0 {
   488  			continue
   489  		}
   490  		defcaus[name.DefCausName.L] = values
   491  	}
   492  	return remained, skipRequest, defcaus
   493  }
   494  
   495  func (helper extractHelper) convertToTime(t int64) time.Time {
   496  	if t == 0 || t == math.MaxInt64 {
   497  		return time.Now()
   498  	}
   499  	return time.Unix(0, t)
   500  }
   501  
   502  // ClusterBlockExtractor is used to extract some predicates of cluster causet.
   503  type ClusterBlockExtractor struct {
   504  	extractHelper
   505  
   506  	// SkipRequest means the where clause always false, we don't need to request any component
   507  	SkipRequest bool
   508  
   509  	// NodeTypes represents all components types we should send request to.
   510  	// e.g:
   511  	// 1. SELECT * FROM cluster_config WHERE type='einsteindb'
   512  	// 2. SELECT * FROM cluster_config WHERE type in ('einsteindb', 'milevadb')
   513  	NodeTypes set.StringSet
   514  
   515  	// Instances represents all components instances we should send request to.
   516  	// e.g:
   517  	// 1. SELECT * FROM cluster_config WHERE instance='192.168.1.7:2379'
   518  	// 2. SELECT * FROM cluster_config WHERE type in ('192.168.1.7:2379', '192.168.1.9:2379')
   519  	Instances set.StringSet
   520  }
   521  
   522  // Extract implements the MemBlockPredicateExtractor Extract interface
   523  func (e *ClusterBlockExtractor) Extract(_ stochastikctx.Context,
   524  	schemaReplicant *memex.Schema,
   525  	names []*types.FieldName,
   526  	predicates []memex.Expression,
   527  ) []memex.Expression {
   528  	remained, typeSkipRequest, nodeTypes := e.extractDefCaus(schemaReplicant, names, predicates, "type", true)
   529  	remained, addrSkipRequest, instances := e.extractDefCaus(schemaReplicant, names, remained, "instance", false)
   530  	e.SkipRequest = typeSkipRequest || addrSkipRequest
   531  	e.NodeTypes = nodeTypes
   532  	e.Instances = instances
   533  	return remained
   534  }
   535  
   536  func (e *ClusterBlockExtractor) explainInfo(p *PhysicalMemBlock) string {
   537  	if e.SkipRequest {
   538  		return "skip_request:true"
   539  	}
   540  	r := new(bytes.Buffer)
   541  	if len(e.NodeTypes) > 0 {
   542  		r.WriteString(fmt.Sprintf("node_types:[%s], ", extractStringFromStringSet(e.NodeTypes)))
   543  	}
   544  	if len(e.Instances) > 0 {
   545  		r.WriteString(fmt.Sprintf("instances:[%s], ", extractStringFromStringSet(e.Instances)))
   546  	}
   547  	// remove the last ", " in the message info
   548  	s := r.String()
   549  	if len(s) > 2 {
   550  		return s[:len(s)-2]
   551  	}
   552  	return s
   553  }
   554  
   555  // ClusterLogBlockExtractor is used to extract some predicates of `cluster_config`
   556  type ClusterLogBlockExtractor struct {
   557  	extractHelper
   558  
   559  	// SkipRequest means the where clause always false, we don't need to request any component
   560  	SkipRequest bool
   561  
   562  	// NodeTypes represents all components types we should send request to.
   563  	// e.g:
   564  	// 1. SELECT * FROM cluster_log WHERE type='einsteindb'
   565  	// 2. SELECT * FROM cluster_log WHERE type in ('einsteindb', 'milevadb')
   566  	NodeTypes set.StringSet
   567  
   568  	// Instances represents all components instances we should send request to.
   569  	// e.g:
   570  	// 1. SELECT * FROM cluster_log WHERE instance='192.168.1.7:2379'
   571  	// 2. SELECT * FROM cluster_log WHERE instance in ('192.168.1.7:2379', '192.168.1.9:2379')
   572  	Instances set.StringSet
   573  
   574  	// StartTime represents the beginning time of log message
   575  	// e.g: SELECT * FROM cluster_log WHERE time>'2020-10-10 10:10:10.999'
   576  	StartTime int64
   577  	// EndTime represents the ending time of log message
   578  	// e.g: SELECT * FROM cluster_log WHERE time<'2020-10-11 10:10:10.999'
   579  	EndTime int64
   580  	// Pattern is used to filter the log message
   581  	// e.g:
   582  	// 1. SELECT * FROM cluster_log WHERE message like '%gc%'
   583  	// 2. SELECT * FROM cluster_log WHERE message regexp '.*'
   584  	Patterns  []string
   585  	LogLevels set.StringSet
   586  }
   587  
   588  // Extract implements the MemBlockPredicateExtractor Extract interface
   589  func (e *ClusterLogBlockExtractor) Extract(
   590  	ctx stochastikctx.Context,
   591  	schemaReplicant *memex.Schema,
   592  	names []*types.FieldName,
   593  	predicates []memex.Expression,
   594  ) []memex.Expression {
   595  	// Extract the `type/instance` columns
   596  	remained, typeSkipRequest, nodeTypes := e.extractDefCaus(schemaReplicant, names, predicates, "type", true)
   597  	remained, addrSkipRequest, instances := e.extractDefCaus(schemaReplicant, names, remained, "instance", false)
   598  	remained, levlSkipRequest, logLevels := e.extractDefCaus(schemaReplicant, names, remained, "level", true)
   599  	e.SkipRequest = typeSkipRequest || addrSkipRequest || levlSkipRequest
   600  	e.NodeTypes = nodeTypes
   601  	e.Instances = instances
   602  	e.LogLevels = logLevels
   603  	if e.SkipRequest {
   604  		return nil
   605  	}
   606  
   607  	remained, startTime, endTime := e.extractTimeRange(ctx, schemaReplicant, names, remained, "time", time.Local)
   608  	// The time unit for search log is millisecond.
   609  	startTime = startTime / int64(time.Millisecond)
   610  	endTime = endTime / int64(time.Millisecond)
   611  	e.StartTime = startTime
   612  	e.EndTime = endTime
   613  	if startTime != 0 && endTime != 0 {
   614  		e.SkipRequest = startTime > endTime
   615  	}
   616  
   617  	if e.SkipRequest {
   618  		return nil
   619  	}
   620  
   621  	remained, patterns := e.extractLikePatternDefCaus(schemaReplicant, names, remained, "message")
   622  	e.Patterns = patterns
   623  	return remained
   624  }
   625  
   626  func (e *ClusterLogBlockExtractor) explainInfo(p *PhysicalMemBlock) string {
   627  	if e.SkipRequest {
   628  		return "skip_request: true"
   629  	}
   630  	r := new(bytes.Buffer)
   631  	st, et := e.StartTime, e.EndTime
   632  	if st > 0 {
   633  		st := time.Unix(0, st*1e6)
   634  		r.WriteString(fmt.Sprintf("start_time:%v, ", st.In(p.ctx.GetStochastikVars().StmtCtx.TimeZone).Format(MetricBlockTimeFormat)))
   635  	}
   636  	if et > 0 {
   637  		et := time.Unix(0, et*1e6)
   638  		r.WriteString(fmt.Sprintf("end_time:%v, ", et.In(p.ctx.GetStochastikVars().StmtCtx.TimeZone).Format(MetricBlockTimeFormat)))
   639  	}
   640  	if len(e.NodeTypes) > 0 {
   641  		r.WriteString(fmt.Sprintf("node_types:[%s], ", extractStringFromStringSet(e.NodeTypes)))
   642  	}
   643  	if len(e.Instances) > 0 {
   644  		r.WriteString(fmt.Sprintf("instances:[%s], ", extractStringFromStringSet(e.Instances)))
   645  	}
   646  	if len(e.LogLevels) > 0 {
   647  		r.WriteString(fmt.Sprintf("log_levels:[%s], ", extractStringFromStringSet(e.LogLevels)))
   648  	}
   649  
   650  	// remove the last ", " in the message info
   651  	s := r.String()
   652  	if len(s) > 2 {
   653  		return s[:len(s)-2]
   654  	}
   655  	return s
   656  }
   657  
   658  // MetricBlockExtractor is used to extract some predicates of metrics_schema blocks.
   659  type MetricBlockExtractor struct {
   660  	extractHelper
   661  	// SkipRequest means the where clause always false, we don't need to request any component
   662  	SkipRequest bool
   663  	// StartTime represents the beginning time of metric data.
   664  	StartTime time.Time
   665  	// EndTime represents the ending time of metric data.
   666  	EndTime time.Time
   667  	// LabelConditions represents the label conditions of metric data.
   668  	LabelConditions map[string]set.StringSet
   669  	Quantiles       []float64
   670  }
   671  
   672  func newMetricBlockExtractor() *MetricBlockExtractor {
   673  	e := &MetricBlockExtractor{}
   674  	e.StartTime, e.EndTime = e.getTimeRange(0, 0)
   675  	return e
   676  }
   677  
   678  // Extract implements the MemBlockPredicateExtractor Extract interface
   679  func (e *MetricBlockExtractor) Extract(
   680  	ctx stochastikctx.Context,
   681  	schemaReplicant *memex.Schema,
   682  	names []*types.FieldName,
   683  	predicates []memex.Expression,
   684  ) []memex.Expression {
   685  	// Extract the `quantile` columns
   686  	remained, skipRequest, quantileSet := e.extractDefCaus(schemaReplicant, names, predicates, "quantile", true)
   687  	e.Quantiles = e.parseQuantiles(quantileSet)
   688  	e.SkipRequest = skipRequest
   689  	if e.SkipRequest {
   690  		return nil
   691  	}
   692  
   693  	// Extract the `time` columns
   694  	remained, startTime, endTime := e.extractTimeRange(ctx, schemaReplicant, names, remained, "time", ctx.GetStochastikVars().StmtCtx.TimeZone)
   695  	e.StartTime, e.EndTime = e.getTimeRange(startTime, endTime)
   696  	e.SkipRequest = e.StartTime.After(e.EndTime)
   697  	if e.SkipRequest {
   698  		return nil
   699  	}
   700  
   701  	excludeDefCauss := set.NewStringSet("quantile", "time", "value")
   702  	_, skipRequest, extractDefCauss := e.extractDefCauss(schemaReplicant, names, remained, excludeDefCauss, false)
   703  	e.SkipRequest = skipRequest
   704  	if e.SkipRequest {
   705  		return nil
   706  	}
   707  	e.LabelConditions = extractDefCauss
   708  	// For some metric, the metric reader can't use the predicate, so keep all label conditions remained.
   709  	return remained
   710  }
   711  
   712  func (e *MetricBlockExtractor) getTimeRange(start, end int64) (time.Time, time.Time) {
   713  	const defaultMetricQueryDuration = 10 * time.Minute
   714  	var startTime, endTime time.Time
   715  	if start == 0 && end == 0 {
   716  		endTime = time.Now()
   717  		return endTime.Add(-defaultMetricQueryDuration), endTime
   718  	}
   719  	if start != 0 {
   720  		startTime = e.convertToTime(start)
   721  	}
   722  	if end != 0 {
   723  		endTime = e.convertToTime(end)
   724  	}
   725  	if start == 0 {
   726  		startTime = endTime.Add(-defaultMetricQueryDuration)
   727  	}
   728  	if end == 0 {
   729  		endTime = startTime.Add(defaultMetricQueryDuration)
   730  	}
   731  	return startTime, endTime
   732  }
   733  
   734  func (e *MetricBlockExtractor) explainInfo(p *PhysicalMemBlock) string {
   735  	if e.SkipRequest {
   736  		return "skip_request: true"
   737  	}
   738  	promQL := e.GetMetricBlockPromQL(p.ctx, p.Block.Name.L)
   739  	startTime, endTime := e.StartTime, e.EndTime
   740  	step := time.Second * time.Duration(p.ctx.GetStochastikVars().MetricSchemaStep)
   741  	return fmt.Sprintf("PromQL:%v, start_time:%v, end_time:%v, step:%v",
   742  		promQL,
   743  		startTime.In(p.ctx.GetStochastikVars().StmtCtx.TimeZone).Format(MetricBlockTimeFormat),
   744  		endTime.In(p.ctx.GetStochastikVars().StmtCtx.TimeZone).Format(MetricBlockTimeFormat),
   745  		step,
   746  	)
   747  }
   748  
   749  // GetMetricBlockPromQL uses to get the promQL of metric causet.
   750  func (e *MetricBlockExtractor) GetMetricBlockPromQL(sctx stochastikctx.Context, lowerBlockName string) string {
   751  	quantiles := e.Quantiles
   752  	def, err := schemareplicant.GetMetricBlockDef(lowerBlockName)
   753  	if err != nil {
   754  		return ""
   755  	}
   756  	if len(quantiles) == 0 {
   757  		quantiles = []float64{def.Quantile}
   758  	}
   759  	var buf bytes.Buffer
   760  	for i, quantile := range quantiles {
   761  		promQL := def.GenPromQL(sctx, e.LabelConditions, quantile)
   762  		if i > 0 {
   763  			buf.WriteByte(',')
   764  		}
   765  		buf.WriteString(promQL)
   766  	}
   767  	return buf.String()
   768  }
   769  
   770  // MetricSummaryBlockExtractor is used to extract some predicates of metrics_schema blocks.
   771  type MetricSummaryBlockExtractor struct {
   772  	extractHelper
   773  	// SkipRequest means the where clause always false, we don't need to request any component
   774  	SkipRequest  bool
   775  	MetricsNames set.StringSet
   776  	Quantiles    []float64
   777  }
   778  
   779  // Extract implements the MemBlockPredicateExtractor Extract interface
   780  func (e *MetricSummaryBlockExtractor) Extract(
   781  	_ stochastikctx.Context,
   782  	schemaReplicant *memex.Schema,
   783  	names []*types.FieldName,
   784  	predicates []memex.Expression,
   785  ) (remained []memex.Expression) {
   786  	remained, quantileSkip, quantiles := e.extractDefCaus(schemaReplicant, names, predicates, "quantile", false)
   787  	remained, metricsNameSkip, metricsNames := e.extractDefCaus(schemaReplicant, names, predicates, "metrics_name", true)
   788  	e.SkipRequest = quantileSkip || metricsNameSkip
   789  	e.Quantiles = e.parseQuantiles(quantiles)
   790  	e.MetricsNames = metricsNames
   791  	return remained
   792  }
   793  
   794  func (e *MetricSummaryBlockExtractor) explainInfo(p *PhysicalMemBlock) string {
   795  	return ""
   796  }
   797  
   798  // InspectionResultBlockExtractor is used to extract some predicates of `inspection_result`
   799  type InspectionResultBlockExtractor struct {
   800  	extractHelper
   801  	// SkipInspection means the where clause always false, we don't need to request any component
   802  	SkipInspection bool
   803  	// Memrules represents rules applied to, and we should apply all inspection rules if there is no rules specified
   804  	// e.g: SELECT * FROM inspection_result WHERE rule in ('dbs', 'config')
   805  	Memrules set.StringSet
   806  	// Items represents items applied to, and we should apply all inspection item if there is no rules specified
   807  	// e.g: SELECT * FROM inspection_result WHERE item in ('dbs.lease', 'raftstore.threadpool')
   808  	Items set.StringSet
   809  }
   810  
   811  // Extract implements the MemBlockPredicateExtractor Extract interface
   812  func (e *InspectionResultBlockExtractor) Extract(
   813  	_ stochastikctx.Context,
   814  	schemaReplicant *memex.Schema,
   815  	names []*types.FieldName,
   816  	predicates []memex.Expression,
   817  ) (remained []memex.Expression) {
   818  	// Extract the `rule/item` columns
   819  	remained, ruleSkip, rules := e.extractDefCaus(schemaReplicant, names, predicates, "rule", true)
   820  	remained, itemSkip, items := e.extractDefCaus(schemaReplicant, names, remained, "item", true)
   821  	e.SkipInspection = ruleSkip || itemSkip
   822  	e.Memrules = rules
   823  	e.Items = items
   824  	return remained
   825  }
   826  
   827  func (e *InspectionResultBlockExtractor) explainInfo(p *PhysicalMemBlock) string {
   828  	if e.SkipInspection {
   829  		return "skip_inspection:true"
   830  	}
   831  	s := make([]string, 0, 2)
   832  	s = append(s, fmt.Sprintf("rules:[%s]", extractStringFromStringSet(e.Memrules)))
   833  	s = append(s, fmt.Sprintf("items:[%s]", extractStringFromStringSet(e.Items)))
   834  	return strings.Join(s, ", ")
   835  }
   836  
   837  // InspectionSummaryBlockExtractor is used to extract some predicates of `inspection_summary`
   838  type InspectionSummaryBlockExtractor struct {
   839  	extractHelper
   840  	// SkipInspection means the where clause always false, we don't need to request any component
   841  	SkipInspection bool
   842  	// Memrules represents rules applied to, and we should apply all inspection rules if there is no rules specified
   843  	// e.g: SELECT * FROM inspection_summary WHERE rule in ('dbs', 'config')
   844  	Memrules    set.StringSet
   845  	MetricNames set.StringSet
   846  	Quantiles   []float64
   847  }
   848  
   849  // Extract implements the MemBlockPredicateExtractor Extract interface
   850  func (e *InspectionSummaryBlockExtractor) Extract(
   851  	_ stochastikctx.Context,
   852  	schemaReplicant *memex.Schema,
   853  	names []*types.FieldName,
   854  	predicates []memex.Expression,
   855  ) (remained []memex.Expression) {
   856  	// Extract the `rule` columns
   857  	_, ruleSkip, rules := e.extractDefCaus(schemaReplicant, names, predicates, "rule", true)
   858  	// Extract the `metric_name` columns
   859  	_, metricNameSkip, metricNames := e.extractDefCaus(schemaReplicant, names, predicates, "metrics_name", true)
   860  	// Extract the `quantile` columns
   861  	remained, quantileSkip, quantileSet := e.extractDefCaus(schemaReplicant, names, predicates, "quantile", false)
   862  	e.SkipInspection = ruleSkip || quantileSkip || metricNameSkip
   863  	e.Memrules = rules
   864  	e.Quantiles = e.parseQuantiles(quantileSet)
   865  	e.MetricNames = metricNames
   866  	return remained
   867  }
   868  
   869  func (e *InspectionSummaryBlockExtractor) explainInfo(p *PhysicalMemBlock) string {
   870  	if e.SkipInspection {
   871  		return "skip_inspection: true"
   872  	}
   873  
   874  	r := new(bytes.Buffer)
   875  	if len(e.Memrules) > 0 {
   876  		r.WriteString(fmt.Sprintf("rules:[%s], ", extractStringFromStringSet(e.Memrules)))
   877  	}
   878  	if len(e.MetricNames) > 0 {
   879  		r.WriteString(fmt.Sprintf("metric_names:[%s], ", extractStringFromStringSet(e.MetricNames)))
   880  	}
   881  	if len(e.Quantiles) > 0 {
   882  		r.WriteString("quantiles:[")
   883  		for i, quantile := range e.Quantiles {
   884  			if i > 0 {
   885  				r.WriteByte(',')
   886  			}
   887  			r.WriteString(fmt.Sprintf("%f", quantile))
   888  		}
   889  		r.WriteString("], ")
   890  	}
   891  
   892  	// remove the last ", " in the message info
   893  	s := r.String()
   894  	if len(s) > 2 {
   895  		return s[:len(s)-2]
   896  	}
   897  	return s
   898  }
   899  
   900  // InspectionMemruleBlockExtractor is used to extract some predicates of `inspection_rules`
   901  type InspectionMemruleBlockExtractor struct {
   902  	extractHelper
   903  
   904  	SkipRequest bool
   905  	Types       set.StringSet
   906  }
   907  
   908  // Extract implements the MemBlockPredicateExtractor Extract interface
   909  func (e *InspectionMemruleBlockExtractor) Extract(
   910  	_ stochastikctx.Context,
   911  	schemaReplicant *memex.Schema,
   912  	names []*types.FieldName,
   913  	predicates []memex.Expression,
   914  ) (remained []memex.Expression) {
   915  	// Extract the `type` columns
   916  	remained, tpSkip, tps := e.extractDefCaus(schemaReplicant, names, predicates, "type", true)
   917  	e.SkipRequest = tpSkip
   918  	e.Types = tps
   919  	return remained
   920  }
   921  
   922  func (e *InspectionMemruleBlockExtractor) explainInfo(p *PhysicalMemBlock) string {
   923  	if e.SkipRequest {
   924  		return "skip_request: true"
   925  	}
   926  
   927  	r := new(bytes.Buffer)
   928  	if len(e.Types) > 0 {
   929  		r.WriteString(fmt.Sprintf("node_types:[%s]", extractStringFromStringSet(e.Types)))
   930  	}
   931  	return r.String()
   932  }
   933  
   934  // SlowQueryExtractor is used to extract some predicates of `slow_query`
   935  type SlowQueryExtractor struct {
   936  	extractHelper
   937  
   938  	SkipRequest bool
   939  	StartTime   time.Time
   940  	EndTime     time.Time
   941  	// Enable is true means the interlock should use the time range to locate the slow-log file that need to be parsed.
   942  	// Enable is false, means the interlock should keep the behavior compatible with before, which is only parse the
   943  	// current slow-log file.
   944  	Enable bool
   945  }
   946  
   947  // Extract implements the MemBlockPredicateExtractor Extract interface
   948  func (e *SlowQueryExtractor) Extract(
   949  	ctx stochastikctx.Context,
   950  	schemaReplicant *memex.Schema,
   951  	names []*types.FieldName,
   952  	predicates []memex.Expression,
   953  ) []memex.Expression {
   954  	remained, startTime, endTime := e.extractTimeRange(ctx, schemaReplicant, names, predicates, "time", ctx.GetStochastikVars().StmtCtx.TimeZone)
   955  	e.setTimeRange(startTime, endTime)
   956  	e.SkipRequest = e.Enable && e.StartTime.After(e.EndTime)
   957  	if e.SkipRequest {
   958  		return nil
   959  	}
   960  	return remained
   961  }
   962  
   963  func (e *SlowQueryExtractor) setTimeRange(start, end int64) {
   964  	const defaultSlowQueryDuration = 24 * time.Hour
   965  	var startTime, endTime time.Time
   966  	if start == 0 && end == 0 {
   967  		return
   968  	}
   969  	if start != 0 {
   970  		startTime = e.convertToTime(start)
   971  	}
   972  	if end != 0 {
   973  		endTime = e.convertToTime(end)
   974  	}
   975  	if start == 0 {
   976  		startTime = endTime.Add(-defaultSlowQueryDuration)
   977  	}
   978  	if end == 0 {
   979  		endTime = startTime.Add(defaultSlowQueryDuration)
   980  	}
   981  	e.StartTime, e.EndTime = startTime, endTime
   982  	e.Enable = true
   983  }
   984  
   985  // BlockStorageStatsExtractor is used to extract some predicates of `disk_usage`.
   986  type BlockStorageStatsExtractor struct {
   987  	extractHelper
   988  	// SkipRequest means the where clause always false, we don't need to request any component.
   989  	SkipRequest bool
   990  	// BlockSchema represents blockSchema applied to, and we should apply all causet disk usage if there is no schemaReplicant specified.
   991  	// e.g: SELECT * FROM information_schema.disk_usage WHERE block_schema in ('test', 'information_schema').
   992  	BlockSchema set.StringSet
   993  	// BlockName represents blockName applied to, and we should apply all causet disk usage if there is no causet specified.
   994  	// e.g: SELECT * FROM information_schema.disk_usage WHERE causet in ('schemata', 'blocks').
   995  	BlockName set.StringSet
   996  }
   997  
   998  // Extract implements the MemBlockPredicateExtractor Extract interface.
   999  func (e *BlockStorageStatsExtractor) Extract(
  1000  	_ stochastikctx.Context,
  1001  	schemaReplicant *memex.Schema,
  1002  	names []*types.FieldName,
  1003  	predicates []memex.Expression,
  1004  ) []memex.Expression {
  1005  	// Extract the `block_schema` columns.
  1006  	remained, schemaSkip, blockSchema := e.extractDefCaus(schemaReplicant, names, predicates, "block_schema", true)
  1007  	// Extract the `block_name` columns.
  1008  	remained, blockSkip, blockName := e.extractDefCaus(schemaReplicant, names, remained, "block_name", true)
  1009  	e.SkipRequest = schemaSkip || blockSkip
  1010  	if e.SkipRequest {
  1011  		return nil
  1012  	}
  1013  	e.BlockSchema = blockSchema
  1014  	e.BlockName = blockName
  1015  	return remained
  1016  }
  1017  
  1018  func (e *BlockStorageStatsExtractor) explainInfo(p *PhysicalMemBlock) string {
  1019  	if e.SkipRequest {
  1020  		return "skip_request: true"
  1021  	}
  1022  
  1023  	r := new(bytes.Buffer)
  1024  	if len(e.BlockSchema) > 0 {
  1025  		r.WriteString(fmt.Sprintf("schemaReplicant:[%s]", extractStringFromStringSet(e.BlockSchema)))
  1026  	}
  1027  	if r.Len() > 0 && len(e.BlockName) > 0 {
  1028  		r.WriteString(", ")
  1029  	}
  1030  	if len(e.BlockName) > 0 {
  1031  		r.WriteString(fmt.Sprintf("causet:[%s]", extractStringFromStringSet(e.BlockName)))
  1032  	}
  1033  	return r.String()
  1034  }
  1035  
  1036  func (e *SlowQueryExtractor) explainInfo(p *PhysicalMemBlock) string {
  1037  	if e.SkipRequest {
  1038  		return "skip_request: true"
  1039  	}
  1040  	if !e.Enable {
  1041  		return fmt.Sprintf("only search in the current '%v' file", p.ctx.GetStochastikVars().SlowQueryFile)
  1042  	}
  1043  	startTime := e.StartTime.In(p.ctx.GetStochastikVars().StmtCtx.TimeZone)
  1044  	endTime := e.EndTime.In(p.ctx.GetStochastikVars().StmtCtx.TimeZone)
  1045  	return fmt.Sprintf("start_time:%v, end_time:%v",
  1046  		types.NewTime(types.FromGoTime(startTime), allegrosql.TypeDatetime, types.MaxFsp).String(),
  1047  		types.NewTime(types.FromGoTime(endTime), allegrosql.TypeDatetime, types.MaxFsp).String())
  1048  }
  1049  
  1050  // TiFlashSystemBlockExtractor is used to extract some predicates of tiflash system causet.
  1051  type TiFlashSystemBlockExtractor struct {
  1052  	extractHelper
  1053  
  1054  	// SkipRequest means the where clause always false, we don't need to request any component
  1055  	SkipRequest bool
  1056  	// TiFlashInstances represents all tiflash instances we should send request to.
  1057  	// e.g:
  1058  	// 1. SELECT * FROM information_schema.<block_name> WHERE tiflash_instance='192.168.1.7:3930'
  1059  	// 2. SELECT * FROM information_schema.<block_name> WHERE tiflash_instance in ('192.168.1.7:3930', '192.168.1.9:3930')
  1060  	TiFlashInstances set.StringSet
  1061  	// MilevaDBDatabases represents milevadbDatabases applied to, and we should apply all milevadb database if there is no database specified.
  1062  	// e.g: SELECT * FROM information_schema.<block_name> WHERE milevadb_database in ('test', 'test2').
  1063  	MilevaDBDatabases string
  1064  	// MilevaDBBlocks represents milevadbBlocks applied to, and we should apply all milevadb causet if there is no causet specified.
  1065  	// e.g: SELECT * FROM information_schema.<block_name> WHERE milevadb_block in ('t', 't2').
  1066  	MilevaDBBlocks string
  1067  }
  1068  
  1069  // Extract implements the MemBlockPredicateExtractor Extract interface
  1070  func (e *TiFlashSystemBlockExtractor) Extract(_ stochastikctx.Context,
  1071  	schemaReplicant *memex.Schema,
  1072  	names []*types.FieldName,
  1073  	predicates []memex.Expression,
  1074  ) []memex.Expression {
  1075  	// Extract the `tiflash_instance` columns.
  1076  	remained, instanceSkip, tiflashInstances := e.extractDefCaus(schemaReplicant, names, predicates, "tiflash_instance", false)
  1077  	// Extract the `milevadb_database` columns.
  1078  	remained, databaseSkip, milevadbDatabases := e.extractDefCaus(schemaReplicant, names, remained, "milevadb_database", true)
  1079  	// Extract the `milevadb_block` columns.
  1080  	remained, blockSkip, milevadbBlocks := e.extractDefCaus(schemaReplicant, names, remained, "milevadb_block", true)
  1081  	e.SkipRequest = instanceSkip || databaseSkip || blockSkip
  1082  	if e.SkipRequest {
  1083  		return nil
  1084  	}
  1085  	e.TiFlashInstances = tiflashInstances
  1086  	e.MilevaDBDatabases = extractStringFromStringSet(milevadbDatabases)
  1087  	e.MilevaDBBlocks = extractStringFromStringSet(milevadbBlocks)
  1088  	return remained
  1089  }
  1090  
  1091  func (e *TiFlashSystemBlockExtractor) explainInfo(p *PhysicalMemBlock) string {
  1092  	if e.SkipRequest {
  1093  		return "skip_request:true"
  1094  	}
  1095  	r := new(bytes.Buffer)
  1096  	if len(e.TiFlashInstances) > 0 {
  1097  		r.WriteString(fmt.Sprintf("tiflash_instances:[%s], ", extractStringFromStringSet(e.TiFlashInstances)))
  1098  	}
  1099  	if len(e.MilevaDBDatabases) > 0 {
  1100  		r.WriteString(fmt.Sprintf("milevadb_databases:[%s], ", e.MilevaDBDatabases))
  1101  	}
  1102  	if len(e.MilevaDBBlocks) > 0 {
  1103  		r.WriteString(fmt.Sprintf("milevadb_blocks:[%s], ", e.MilevaDBBlocks))
  1104  	}
  1105  	// remove the last ", " in the message info
  1106  	s := r.String()
  1107  	if len(s) > 2 {
  1108  		return s[:len(s)-2]
  1109  	}
  1110  	return s
  1111  }