github.com/whtcorpsinc/MilevaDB-Prod@v0.0.0-20211104133533-f57f4be3b597/causetstore/petri/acyclic/causet/optimize.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 causet
    15  
    16  import (
    17  	"context"
    18  	"math"
    19  	"runtime/trace"
    20  	"strings"
    21  	"time"
    22  
    23  	"github.com/whtcorpsinc/BerolinaSQL"
    24  	"github.com/whtcorpsinc/BerolinaSQL/ast"
    25  	"github.com/whtcorpsinc/errors"
    26  	"github.com/whtcorpsinc/milevadb/bindinfo"
    27  	"github.com/whtcorpsinc/milevadb/causet/cascades"
    28  	causetembedded "github.com/whtcorpsinc/milevadb/causet/embedded"
    29  	"github.com/whtcorpsinc/milevadb/ekv"
    30  	"github.com/whtcorpsinc/milevadb/metrics"
    31  	"github.com/whtcorpsinc/milevadb/petri"
    32  	"github.com/whtcorpsinc/milevadb/privilege"
    33  	"github.com/whtcorpsinc/milevadb/schemareplicant"
    34  	"github.com/whtcorpsinc/milevadb/soliton/hint"
    35  	"github.com/whtcorpsinc/milevadb/soliton/logutil"
    36  	"github.com/whtcorpsinc/milevadb/stochastikctx"
    37  	"github.com/whtcorpsinc/milevadb/stochastikctx/stmtctx"
    38  	"github.com/whtcorpsinc/milevadb/stochastikctx/variable"
    39  	"github.com/whtcorpsinc/milevadb/types"
    40  	"go.uber.org/zap"
    41  )
    42  
    43  // GetPreparedStmt extract the prepared memex from the execute memex.
    44  func GetPreparedStmt(stmt *ast.InterDircuteStmt, vars *variable.StochastikVars) (ast.StmtNode, error) {
    45  	var ok bool
    46  	execID := stmt.InterDircID
    47  	if stmt.Name != "" {
    48  		if execID, ok = vars.PreparedStmtNameToID[stmt.Name]; !ok {
    49  			return nil, causetembedded.ErrStmtNotFound
    50  		}
    51  	}
    52  	if preparedPointer, ok := vars.PreparedStmts[execID]; ok {
    53  		preparedObj, ok := preparedPointer.(*causetembedded.CachedPrepareStmt)
    54  		if !ok {
    55  			return nil, errors.Errorf("invalid CachedPrepareStmt type")
    56  		}
    57  		return preparedObj.PreparedAst.Stmt, nil
    58  	}
    59  	return nil, causetembedded.ErrStmtNotFound
    60  }
    61  
    62  // IsReadOnly check whether the ast.Node is a read only memex.
    63  func IsReadOnly(node ast.Node, vars *variable.StochastikVars) bool {
    64  	if execStmt, isInterDircStmt := node.(*ast.InterDircuteStmt); isInterDircStmt {
    65  		s, err := GetPreparedStmt(execStmt, vars)
    66  		if err != nil {
    67  			logutil.BgLogger().Warn("GetPreparedStmt failed", zap.Error(err))
    68  			return false
    69  		}
    70  		return ast.IsReadOnly(s)
    71  	}
    72  	return ast.IsReadOnly(node)
    73  }
    74  
    75  // Optimize does optimization and creates a Causet.
    76  // The node must be prepared first.
    77  func Optimize(ctx context.Context, sctx stochastikctx.Context, node ast.Node, is schemareplicant.SchemaReplicant) (causetembedded.Causet, types.NameSlice, error) {
    78  	sessVars := sctx.GetStochastikVars()
    79  
    80  	// Because for write stmt, TiFlash has a different results when dagger the data in point get plan. We ban the TiFlash
    81  	// engine in not read only stmt.
    82  	if _, isolationReadContainTiFlash := sessVars.IsolationReadEngines[ekv.TiFlash]; isolationReadContainTiFlash && !IsReadOnly(node, sessVars) {
    83  		delete(sessVars.IsolationReadEngines, ekv.TiFlash)
    84  		defer func() {
    85  			sessVars.IsolationReadEngines[ekv.TiFlash] = struct{}{}
    86  		}()
    87  	}
    88  
    89  	if _, isolationReadContainEinsteinDB := sessVars.IsolationReadEngines[ekv.EinsteinDB]; isolationReadContainEinsteinDB {
    90  		var fp causetembedded.Causet
    91  		if fpv, ok := sctx.Value(causetembedded.PointCausetKey).(causetembedded.PointCausetVal); ok {
    92  			// point plan is already tried in a multi-memex query.
    93  			fp = fpv.Causet
    94  		} else {
    95  			fp = causetembedded.TryFastCauset(sctx, node)
    96  		}
    97  		if fp != nil {
    98  			if !useMaxTS(sctx, fp) {
    99  				sctx.PrepareTSFuture(ctx)
   100  			}
   101  			return fp, fp.OutputNames(), nil
   102  		}
   103  	}
   104  
   105  	sctx.PrepareTSFuture(ctx)
   106  
   107  	blockHints := hint.ExtractBlockHintsFromStmtNode(node, sctx)
   108  	stmtHints, warns := handleStmtHints(blockHints)
   109  	sessVars.StmtCtx.StmtHints = stmtHints
   110  	for _, warn := range warns {
   111  		sctx.GetStochastikVars().StmtCtx.AppendWarning(warn)
   112  	}
   113  	warns = warns[:0]
   114  	bestCauset, names, _, err := optimize(ctx, sctx, node, is)
   115  	if err != nil {
   116  		return nil, nil, err
   117  	}
   118  	if !(sessVars.UseCausetBaselines || sessVars.EvolveCausetBaselines) {
   119  		return bestCauset, names, nil
   120  	}
   121  	stmtNode, ok := node.(ast.StmtNode)
   122  	if !ok {
   123  		return bestCauset, names, nil
   124  	}
   125  	bindRecord, scope := getBindRecord(sctx, stmtNode)
   126  	if bindRecord == nil {
   127  		return bestCauset, names, nil
   128  	}
   129  	if sctx.GetStochastikVars().SelectLimit != math.MaxUint64 {
   130  		sctx.GetStochastikVars().StmtCtx.AppendWarning(errors.New("sql_select_limit is set, so plan binding is not activated"))
   131  		return bestCauset, names, nil
   132  	}
   133  	bestCausetHint := causetembedded.GenHintsFromPhysicalCauset(bestCauset)
   134  	if len(bindRecord.Bindings) > 0 {
   135  		orgBinding := bindRecord.Bindings[0] // the first is the original binding
   136  		for _, tbHint := range blockHints {  // consider causet hints which contained by the original binding
   137  			if orgBinding.Hint.ContainBlockHint(tbHint.HintName.String()) {
   138  				bestCausetHint = append(bestCausetHint, tbHint)
   139  			}
   140  		}
   141  	}
   142  	bestCausetHintStr := hint.RestoreOptimizerHints(bestCausetHint)
   143  
   144  	defer func() {
   145  		sessVars.StmtCtx.StmtHints = stmtHints
   146  		for _, warn := range warns {
   147  			sctx.GetStochastikVars().StmtCtx.AppendWarning(warn)
   148  		}
   149  	}()
   150  	binding := bindRecord.FindBinding(bestCausetHintStr)
   151  	// If the best bestCauset is in baselines, just use it.
   152  	if binding != nil && binding.Status == bindinfo.Using {
   153  		if sctx.GetStochastikVars().UseCausetBaselines {
   154  			stmtHints, warns = handleStmtHints(binding.Hint.GetFirstBlockHints())
   155  		}
   156  		return bestCauset, names, nil
   157  	}
   158  	bestCostAmongHints := math.MaxFloat64
   159  	var bestCausetAmongHints causetembedded.Causet
   160  	originHints := hint.DefCauslectHint(stmtNode)
   161  	// Try to find the best binding.
   162  	for _, binding := range bindRecord.Bindings {
   163  		if binding.Status != bindinfo.Using {
   164  			continue
   165  		}
   166  		metrics.BindUsageCounter.WithLabelValues(scope).Inc()
   167  		hint.BindHint(stmtNode, binding.Hint)
   168  		curStmtHints, curWarns := handleStmtHints(binding.Hint.GetFirstBlockHints())
   169  		sctx.GetStochastikVars().StmtCtx.StmtHints = curStmtHints
   170  		plan, _, cost, err := optimize(ctx, sctx, node, is)
   171  		if err != nil {
   172  			binding.Status = bindinfo.Invalid
   173  			handleInvalidBindRecord(ctx, sctx, scope, bindinfo.BindRecord{
   174  				OriginalALLEGROSQL: bindRecord.OriginalALLEGROSQL,
   175  				EDB:                bindRecord.EDB,
   176  				Bindings:           []bindinfo.Binding{binding},
   177  			})
   178  			continue
   179  		}
   180  		if cost < bestCostAmongHints {
   181  			if sctx.GetStochastikVars().UseCausetBaselines {
   182  				stmtHints, warns = curStmtHints, curWarns
   183  			}
   184  			bestCostAmongHints = cost
   185  			bestCausetAmongHints = plan
   186  		}
   187  	}
   188  	// 1. If there is already a evolution task, we do not need to handle it again.
   189  	// 2. If the origin binding contain `read_from_storage` hint, we should ignore the evolve task.
   190  	// 3. If the best plan contain TiFlash hint, we should ignore the evolve task.
   191  	if sctx.GetStochastikVars().EvolveCausetBaselines && binding == nil &&
   192  		!originHints.ContainBlockHint(causetembedded.HintReadFromStorage) &&
   193  		!bindRecord.Bindings[0].Hint.ContainBlockHint(causetembedded.HintReadFromStorage) {
   194  		handleEvolveTasks(ctx, sctx, bindRecord, stmtNode, bestCausetHintStr)
   195  	}
   196  	// Restore the hint to avoid changing the stmt node.
   197  	hint.BindHint(stmtNode, originHints)
   198  	if sctx.GetStochastikVars().UseCausetBaselines && bestCausetAmongHints != nil {
   199  		return bestCausetAmongHints, names, nil
   200  	}
   201  	return bestCauset, names, nil
   202  }
   203  
   204  func optimize(ctx context.Context, sctx stochastikctx.Context, node ast.Node, is schemareplicant.SchemaReplicant) (causetembedded.Causet, types.NameSlice, float64, error) {
   205  	// build logical plan
   206  	sctx.GetStochastikVars().CausetID = 0
   207  	sctx.GetStochastikVars().CausetDeferredCausetID = 0
   208  	hintProcessor := &hint.BlockHintProcessor{Ctx: sctx}
   209  	node.Accept(hintProcessor)
   210  	builder := causetembedded.NewCausetBuilder(sctx, is, hintProcessor)
   211  
   212  	// reset fields about rewrite
   213  	sctx.GetStochastikVars().RewritePhaseInfo.Reset()
   214  	beginRewrite := time.Now()
   215  	p, err := builder.Build(ctx, node)
   216  	if err != nil {
   217  		return nil, nil, 0, err
   218  	}
   219  	sctx.GetStochastikVars().RewritePhaseInfo.DurationRewrite = time.Since(beginRewrite)
   220  
   221  	sctx.GetStochastikVars().StmtCtx.Blocks = builder.GetDBBlockInfo()
   222  	activeRoles := sctx.GetStochastikVars().ActiveRoles
   223  	// Check privilege. Maybe it's better to move this to the Preprocess, but
   224  	// we need the causet information to check privilege, which is collected
   225  	// into the visitInfo in the logical plan builder.
   226  	if pm := privilege.GetPrivilegeManager(sctx); pm != nil {
   227  		if err := causetembedded.CheckPrivilege(activeRoles, pm, builder.GetVisitInfo()); err != nil {
   228  			return nil, nil, 0, err
   229  		}
   230  	}
   231  
   232  	if err := causetembedded.CheckBlockLock(sctx, is, builder.GetVisitInfo()); err != nil {
   233  		return nil, nil, 0, err
   234  	}
   235  
   236  	// Handle the execute memex.
   237  	if execCauset, ok := p.(*causetembedded.InterDircute); ok {
   238  		err := execCauset.OptimizePreparedCauset(ctx, sctx, is)
   239  		return p, p.OutputNames(), 0, err
   240  	}
   241  
   242  	names := p.OutputNames()
   243  
   244  	// Handle the non-logical plan memex.
   245  	logic, isLogicalCauset := p.(causetembedded.LogicalCauset)
   246  	if !isLogicalCauset {
   247  		return p, names, 0, nil
   248  	}
   249  
   250  	// Handle the logical plan memex, use cascades causet if enabled.
   251  	if sctx.GetStochastikVars().GetEnableCascadesCausetAppend() {
   252  		finalCauset, cost, err := cascades.DefaultOptimizer.FindBestCauset(sctx, logic)
   253  		return finalCauset, names, cost, err
   254  	}
   255  
   256  	beginOpt := time.Now()
   257  	finalCauset, cost, err := causetembedded.DoOptimize(ctx, sctx, builder.GetOptFlag(), logic)
   258  	sctx.GetStochastikVars().DurationOptimization = time.Since(beginOpt)
   259  	return finalCauset, names, cost, err
   260  }
   261  
   262  func extractSelectAndNormalizeDigest(stmtNode ast.StmtNode) (*ast.SelectStmt, string, string) {
   263  	switch x := stmtNode.(type) {
   264  	case *ast.ExplainStmt:
   265  		switch x.Stmt.(type) {
   266  		case *ast.SelectStmt:
   267  			causetembedded.EraseLastSemicolon(x)
   268  			normalizeExplainALLEGROSQL := BerolinaSQL.Normalize(x.Text())
   269  			idx := strings.Index(normalizeExplainALLEGROSQL, "select")
   270  			normalizeALLEGROSQL := normalizeExplainALLEGROSQL[idx:]
   271  			hash := BerolinaSQL.DigestNormalized(normalizeALLEGROSQL)
   272  			return x.Stmt.(*ast.SelectStmt), normalizeALLEGROSQL, hash
   273  		}
   274  	case *ast.SelectStmt:
   275  		causetembedded.EraseLastSemicolon(x)
   276  		normalizedALLEGROSQL, hash := BerolinaSQL.NormalizeDigest(x.Text())
   277  		return x, normalizedALLEGROSQL, hash
   278  	}
   279  	return nil, "", ""
   280  }
   281  
   282  func getBindRecord(ctx stochastikctx.Context, stmt ast.StmtNode) (*bindinfo.BindRecord, string) {
   283  	// When the petri is initializing, the bind will be nil.
   284  	if ctx.Value(bindinfo.StochastikBindInfoKeyType) == nil {
   285  		return nil, ""
   286  	}
   287  	selectStmt, normalizedALLEGROSQL, hash := extractSelectAndNormalizeDigest(stmt)
   288  	if selectStmt == nil {
   289  		return nil, ""
   290  	}
   291  	stochastikHandle := ctx.Value(bindinfo.StochastikBindInfoKeyType).(*bindinfo.StochastikHandle)
   292  	bindRecord := stochastikHandle.GetBindRecord(normalizedALLEGROSQL, ctx.GetStochastikVars().CurrentDB)
   293  	if bindRecord == nil {
   294  		bindRecord = stochastikHandle.GetBindRecord(normalizedALLEGROSQL, "")
   295  	}
   296  	if bindRecord != nil {
   297  		if bindRecord.HasUsingBinding() {
   298  			return bindRecord, metrics.ScopeStochastik
   299  		}
   300  		return nil, ""
   301  	}
   302  	globalHandle := petri.GetPetri(ctx).BindHandle()
   303  	if globalHandle == nil {
   304  		return nil, ""
   305  	}
   306  	bindRecord = globalHandle.GetBindRecord(hash, normalizedALLEGROSQL, ctx.GetStochastikVars().CurrentDB)
   307  	if bindRecord == nil {
   308  		bindRecord = globalHandle.GetBindRecord(hash, normalizedALLEGROSQL, "")
   309  	}
   310  	return bindRecord, metrics.ScopeGlobal
   311  }
   312  
   313  func handleInvalidBindRecord(ctx context.Context, sctx stochastikctx.Context, level string, bindRecord bindinfo.BindRecord) {
   314  	stochastikHandle := sctx.Value(bindinfo.StochastikBindInfoKeyType).(*bindinfo.StochastikHandle)
   315  	err := stochastikHandle.DropBindRecord(bindRecord.OriginalALLEGROSQL, bindRecord.EDB, &bindRecord.Bindings[0])
   316  	if err != nil {
   317  		logutil.Logger(ctx).Info("drop stochastik bindings failed")
   318  	}
   319  	if level == metrics.ScopeStochastik {
   320  		return
   321  	}
   322  
   323  	globalHandle := petri.GetPetri(sctx).BindHandle()
   324  	globalHandle.AddDropInvalidBindTask(&bindRecord)
   325  }
   326  
   327  func handleEvolveTasks(ctx context.Context, sctx stochastikctx.Context, br *bindinfo.BindRecord, stmtNode ast.StmtNode, planHint string) {
   328  	bindALLEGROSQL := bindinfo.GenerateBindALLEGROSQL(ctx, stmtNode, planHint)
   329  	if bindALLEGROSQL == "" {
   330  		return
   331  	}
   332  	charset, collation := sctx.GetStochastikVars().GetCharsetInfo()
   333  	binding := bindinfo.Binding{
   334  		BindALLEGROSQL: bindALLEGROSQL,
   335  		Status:         bindinfo.PendingVerify,
   336  		Charset:        charset,
   337  		DefCauslation:  collation,
   338  		Source:         bindinfo.Evolve,
   339  	}
   340  	globalHandle := petri.GetPetri(sctx).BindHandle()
   341  	globalHandle.AddEvolveCausetTask(br.OriginalALLEGROSQL, br.EDB, binding)
   342  }
   343  
   344  // useMaxTS returns true when meets following conditions:
   345  //  1. ctx is auto commit tagged.
   346  //  2. plan is point get by pk.
   347  func useMaxTS(ctx stochastikctx.Context, p causetembedded.Causet) bool {
   348  	if !causetembedded.IsAutoCommitTxn(ctx) {
   349  		return false
   350  	}
   351  
   352  	v, ok := p.(*causetembedded.PointGetCauset)
   353  	return ok && (v.IndexInfo == nil || (v.IndexInfo.Primary && v.TblInfo.IsCommonHandle))
   354  }
   355  
   356  // OptimizeInterDircStmt to optimize prepare memex protocol "execute" memex
   357  // this is a short path ONLY does things filling prepare related params
   358  // for point select like plan which does not need extra things
   359  func OptimizeInterDircStmt(ctx context.Context, sctx stochastikctx.Context,
   360  	execAst *ast.InterDircuteStmt, is schemareplicant.SchemaReplicant) (causetembedded.Causet, error) {
   361  	defer trace.StartRegion(ctx, "Optimize").End()
   362  	var err error
   363  	builder := causetembedded.NewCausetBuilder(sctx, is, nil)
   364  	p, err := builder.Build(ctx, execAst)
   365  	if err != nil {
   366  		return nil, err
   367  	}
   368  	if execCauset, ok := p.(*causetembedded.InterDircute); ok {
   369  		err = execCauset.OptimizePreparedCauset(ctx, sctx, is)
   370  		return execCauset.Causet, err
   371  	}
   372  	err = errors.Errorf("invalid result plan type, should be InterDircute")
   373  	return nil, err
   374  }
   375  
   376  func handleStmtHints(hints []*ast.BlockOptimizerHint) (stmtHints stmtctx.StmtHints, warns []error) {
   377  	if len(hints) == 0 {
   378  		return
   379  	}
   380  	var memoryQuotaHint, useToJAHint, useCascadesHint, maxInterDircutionTime, forceNthCauset *ast.BlockOptimizerHint
   381  	var memoryQuotaHintCnt, useToJAHintCnt, useCascadesHintCnt, noIndexMergeHintCnt, readReplicaHintCnt, maxInterDircutionTimeCnt, forceNthCausetCnt int
   382  	for _, hint := range hints {
   383  		switch hint.HintName.L {
   384  		case "memory_quota":
   385  			memoryQuotaHint = hint
   386  			memoryQuotaHintCnt++
   387  		case "use_toja":
   388  			useToJAHint = hint
   389  			useToJAHintCnt++
   390  		case "use_cascades":
   391  			useCascadesHint = hint
   392  			useCascadesHintCnt++
   393  		case "no_index_merge":
   394  			noIndexMergeHintCnt++
   395  		case "read_consistent_replica":
   396  			readReplicaHintCnt++
   397  		case "max_execution_time":
   398  			maxInterDircutionTimeCnt++
   399  			maxInterDircutionTime = hint
   400  		case "nth_plan":
   401  			forceNthCausetCnt++
   402  			forceNthCauset = hint
   403  		}
   404  	}
   405  	// Handle MEMORY_QUOTA
   406  	if memoryQuotaHintCnt != 0 {
   407  		if memoryQuotaHintCnt > 1 {
   408  			warn := errors.Errorf("MEMORY_QUOTA() s defined more than once, only the last definition takes effect: MEMORY_QUOTA(%v)", memoryQuotaHint.HintData.(int64))
   409  			warns = append(warns, warn)
   410  		}
   411  		// InterlockingDirectorate use MemoryQuota <= 0 to indicate no memory limit, here use < 0 to handle hint syntax error.
   412  		if memoryQuota := memoryQuotaHint.HintData.(int64); memoryQuota < 0 {
   413  			warn := errors.New("The use of MEMORY_QUOTA hint is invalid, valid usage: MEMORY_QUOTA(10 MB) or MEMORY_QUOTA(10 GB)")
   414  			warns = append(warns, warn)
   415  		} else {
   416  			stmtHints.HasMemQuotaHint = true
   417  			stmtHints.MemQuotaQuery = memoryQuota
   418  			if memoryQuota == 0 {
   419  				warn := errors.New("Setting the MEMORY_QUOTA to 0 means no memory limit")
   420  				warns = append(warns, warn)
   421  			}
   422  		}
   423  	}
   424  	// Handle USE_TOJA
   425  	if useToJAHintCnt != 0 {
   426  		if useToJAHintCnt > 1 {
   427  			warn := errors.Errorf("USE_TOJA() is defined more than once, only the last definition takes effect: USE_TOJA(%v)", useToJAHint.HintData.(bool))
   428  			warns = append(warns, warn)
   429  		}
   430  		stmtHints.HasAllowInSubqToJoinAnPosetDaggHint = true
   431  		stmtHints.AllowInSubqToJoinAnPosetDagg = useToJAHint.HintData.(bool)
   432  	}
   433  	// Handle USE_CASCADES
   434  	if useCascadesHintCnt != 0 {
   435  		if useCascadesHintCnt > 1 {
   436  			warn := errors.Errorf("USE_CASCADES() is defined more than once, only the last definition takes effect: USE_CASCADES(%v)", useCascadesHint.HintData.(bool))
   437  			warns = append(warns, warn)
   438  		}
   439  		stmtHints.HasEnableCascadesCausetAppendHint = true
   440  		stmtHints.EnableCascadesCausetAppend = useCascadesHint.HintData.(bool)
   441  	}
   442  	// Handle NO_INDEX_MERGE
   443  	if noIndexMergeHintCnt != 0 {
   444  		if noIndexMergeHintCnt > 1 {
   445  			warn := errors.New("NO_INDEX_MERGE() is defined more than once, only the last definition takes effect")
   446  			warns = append(warns, warn)
   447  		}
   448  		stmtHints.NoIndexMergeHint = true
   449  	}
   450  	// Handle READ_CONSISTENT_REPLICA
   451  	if readReplicaHintCnt != 0 {
   452  		if readReplicaHintCnt > 1 {
   453  			warn := errors.New("READ_CONSISTENT_REPLICA() is defined more than once, only the last definition takes effect")
   454  			warns = append(warns, warn)
   455  		}
   456  		stmtHints.HasReplicaReadHint = true
   457  		stmtHints.ReplicaRead = byte(ekv.ReplicaReadFollower)
   458  	}
   459  	// Handle MAX_EXECUTION_TIME
   460  	if maxInterDircutionTimeCnt != 0 {
   461  		if maxInterDircutionTimeCnt > 1 {
   462  			warn := errors.Errorf("MAX_EXECUTION_TIME() is defined more than once, only the last definition takes effect: MAX_EXECUTION_TIME(%v)", maxInterDircutionTime.HintData.(uint64))
   463  			warns = append(warns, warn)
   464  		}
   465  		stmtHints.HasMaxInterDircutionTime = true
   466  		stmtHints.MaxInterDircutionTime = maxInterDircutionTime.HintData.(uint64)
   467  	}
   468  	// Handle NTH_PLAN
   469  	if forceNthCausetCnt != 0 {
   470  		if forceNthCausetCnt > 1 {
   471  			warn := errors.Errorf("NTH_PLAN() is defined more than once, only the last definition takes effect: NTH_PLAN(%v)", forceNthCauset.HintData.(int64))
   472  			warns = append(warns, warn)
   473  		}
   474  		stmtHints.ForceNthCauset = forceNthCauset.HintData.(int64)
   475  		if stmtHints.ForceNthCauset < 1 {
   476  			stmtHints.ForceNthCauset = -1
   477  			warn := errors.Errorf("the hintdata for NTH_PLAN() is too small, hint ignored.")
   478  			warns = append(warns, warn)
   479  		}
   480  	} else {
   481  		stmtHints.ForceNthCauset = -1
   482  	}
   483  	return
   484  }
   485  
   486  func init() {
   487  	causetembedded.OptimizeAstNode = Optimize
   488  }