github.com/whtcorpsinc/MilevaDB-Prod@v0.0.0-20211104133533-f57f4be3b597/interlock/prepared.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 interlock
    15  
    16  import (
    17  	"context"
    18  	"math"
    19  	"sort"
    20  	"time"
    21  
    22  	"github.com/whtcorpsinc/BerolinaSQL"
    23  	"github.com/whtcorpsinc/BerolinaSQL/allegrosql"
    24  	"github.com/whtcorpsinc/BerolinaSQL/ast"
    25  	"github.com/whtcorpsinc/errors"
    26  	"github.com/whtcorpsinc/log"
    27  	"github.com/whtcorpsinc/milevadb/causet"
    28  	causetembedded "github.com/whtcorpsinc/milevadb/causet/embedded"
    29  	"github.com/whtcorpsinc/milevadb/memex"
    30  	"github.com/whtcorpsinc/milevadb/schemareplicant"
    31  	"github.com/whtcorpsinc/milevadb/soliton"
    32  	"github.com/whtcorpsinc/milevadb/soliton/chunk"
    33  	"github.com/whtcorpsinc/milevadb/soliton/hint"
    34  	"github.com/whtcorpsinc/milevadb/soliton/sqlexec"
    35  	"github.com/whtcorpsinc/milevadb/stochastikctx"
    36  	"github.com/whtcorpsinc/milevadb/types"
    37  	driver "github.com/whtcorpsinc/milevadb/types/BerolinaSQL_driver"
    38  	"go.uber.org/zap"
    39  )
    40  
    41  var (
    42  	_ InterlockingDirectorate = &DeallocateInterDirc{}
    43  	_ InterlockingDirectorate = &InterDircuteInterDirc{}
    44  	_ InterlockingDirectorate = &PrepareInterDirc{}
    45  )
    46  
    47  type paramMarkerSorter struct {
    48  	markers []ast.ParamMarkerExpr
    49  }
    50  
    51  func (p *paramMarkerSorter) Len() int {
    52  	return len(p.markers)
    53  }
    54  
    55  func (p *paramMarkerSorter) Less(i, j int) bool {
    56  	return p.markers[i].(*driver.ParamMarkerExpr).Offset < p.markers[j].(*driver.ParamMarkerExpr).Offset
    57  }
    58  
    59  func (p *paramMarkerSorter) Swap(i, j int) {
    60  	p.markers[i], p.markers[j] = p.markers[j], p.markers[i]
    61  }
    62  
    63  type paramMarkerExtractor struct {
    64  	markers []ast.ParamMarkerExpr
    65  }
    66  
    67  func (e *paramMarkerExtractor) Enter(in ast.Node) (ast.Node, bool) {
    68  	return in, false
    69  }
    70  
    71  func (e *paramMarkerExtractor) Leave(in ast.Node) (ast.Node, bool) {
    72  	if x, ok := in.(*driver.ParamMarkerExpr); ok {
    73  		e.markers = append(e.markers, x)
    74  	}
    75  	return in, true
    76  }
    77  
    78  // PrepareInterDirc represents a PREPARE interlock.
    79  type PrepareInterDirc struct {
    80  	baseInterlockingDirectorate
    81  
    82  	is      schemareplicant.SchemaReplicant
    83  	name    string
    84  	sqlText string
    85  
    86  	ID         uint32
    87  	ParamCount int
    88  	Fields     []*ast.ResultField
    89  }
    90  
    91  // NewPrepareInterDirc creates a new PrepareInterDirc.
    92  func NewPrepareInterDirc(ctx stochastikctx.Context, is schemareplicant.SchemaReplicant, sqlTxt string) *PrepareInterDirc {
    93  	base := newBaseInterlockingDirectorate(ctx, nil, 0)
    94  	base.initCap = chunk.ZeroCapacity
    95  	return &PrepareInterDirc{
    96  		baseInterlockingDirectorate: base,
    97  		is:                          is,
    98  		sqlText:                     sqlTxt,
    99  	}
   100  }
   101  
   102  // Next implements the InterlockingDirectorate Next interface.
   103  func (e *PrepareInterDirc) Next(ctx context.Context, req *chunk.Chunk) error {
   104  	vars := e.ctx.GetStochastikVars()
   105  	if e.ID != 0 {
   106  		// Must be the case when we retry a prepare.
   107  		// Make sure it is idempotent.
   108  		_, ok := vars.PreparedStmts[e.ID]
   109  		if ok {
   110  			return nil
   111  		}
   112  	}
   113  	charset, defCauslation := vars.GetCharsetInfo()
   114  	var (
   115  		stmts []ast.StmtNode
   116  		err   error
   117  	)
   118  	if sqlBerolinaSQL, ok := e.ctx.(sqlexec.ALLEGROSQLBerolinaSQL); ok {
   119  		stmts, err = sqlBerolinaSQL.ParseALLEGROSQL(e.sqlText, charset, defCauslation)
   120  	} else {
   121  		p := BerolinaSQL.New()
   122  		p.EnableWindowFunc(vars.EnableWindowFunction)
   123  		var warns []error
   124  		stmts, warns, err = p.Parse(e.sqlText, charset, defCauslation)
   125  		for _, warn := range warns {
   126  			e.ctx.GetStochastikVars().StmtCtx.AppendWarning(soliton.SyntaxWarn(warn))
   127  		}
   128  	}
   129  	if err != nil {
   130  		return soliton.SyntaxError(err)
   131  	}
   132  	if len(stmts) != 1 {
   133  		return ErrPrepareMulti
   134  	}
   135  	stmt := stmts[0]
   136  
   137  	err = ResetContextOfStmt(e.ctx, stmt)
   138  	if err != nil {
   139  		return err
   140  	}
   141  
   142  	var extractor paramMarkerExtractor
   143  	stmt.Accept(&extractor)
   144  
   145  	// DBS Statements can not accept parameters
   146  	if _, ok := stmt.(ast.DBSNode); ok && len(extractor.markers) > 0 {
   147  		return ErrPrepareDBS
   148  	}
   149  
   150  	// Prepare parameters should NOT over 2 bytes(MaxUint16)
   151  	// https://dev.allegrosql.com/doc/internals/en/com-stmt-prepare-response.html#packet-COM_STMT_PREPARE_OK.
   152  	if len(extractor.markers) > math.MaxUint16 {
   153  		return ErrPsManyParam
   154  	}
   155  
   156  	err = causetembedded.Preprocess(e.ctx, stmt, e.is, causetembedded.InPrepare)
   157  	if err != nil {
   158  		return err
   159  	}
   160  
   161  	// The parameter markers are appended in visiting order, which may not
   162  	// be the same as the position order in the query string. We need to
   163  	// sort it by position.
   164  	sorter := &paramMarkerSorter{markers: extractor.markers}
   165  	sort.Sort(sorter)
   166  	e.ParamCount = len(sorter.markers)
   167  	for i := 0; i < e.ParamCount; i++ {
   168  		sorter.markers[i].SetOrder(i)
   169  	}
   170  	prepared := &ast.Prepared{
   171  		Stmt:          stmt,
   172  		StmtType:      GetStmtLabel(stmt),
   173  		Params:        sorter.markers,
   174  		SchemaVersion: e.is.SchemaMetaVersion(),
   175  	}
   176  
   177  	if !causetembedded.PreparedCausetCacheEnabled() {
   178  		prepared.UseCache = false
   179  	} else {
   180  		if !e.ctx.GetStochastikVars().UseDynamicPartitionPrune() {
   181  			prepared.UseCache = causetembedded.Cacheable(stmt, e.is)
   182  		} else {
   183  			prepared.UseCache = causetembedded.Cacheable(stmt, nil)
   184  		}
   185  	}
   186  
   187  	// We try to build the real memex of preparedStmt.
   188  	for i := range prepared.Params {
   189  		param := prepared.Params[i].(*driver.ParamMarkerExpr)
   190  		param.Causet.SetNull()
   191  		param.InInterDircute = false
   192  	}
   193  	var p causetembedded.Causet
   194  	e.ctx.GetStochastikVars().CausetID = 0
   195  	e.ctx.GetStochastikVars().CausetDeferredCausetID = 0
   196  	destBuilder := causetembedded.NewCausetBuilder(e.ctx, e.is, &hint.BlockHintProcessor{})
   197  	p, err = destBuilder.Build(ctx, stmt)
   198  	if err != nil {
   199  		return err
   200  	}
   201  	if _, ok := stmt.(*ast.SelectStmt); ok {
   202  		e.Fields = defCausNames2ResultFields(p.Schema(), p.OutputNames(), vars.CurrentDB)
   203  	}
   204  	if e.ID == 0 {
   205  		e.ID = vars.GetNextPreparedStmtID()
   206  	}
   207  	if e.name != "" {
   208  		vars.PreparedStmtNameToID[e.name] = e.ID
   209  	}
   210  
   211  	normalized, digest := BerolinaSQL.NormalizeDigest(prepared.Stmt.Text())
   212  	preparedObj := &causetembedded.CachedPrepareStmt{
   213  		PreparedAst:          prepared,
   214  		VisitInfos:           destBuilder.GetVisitInfo(),
   215  		NormalizedALLEGROSQL: normalized,
   216  		ALLEGROSQLDigest:     digest,
   217  	}
   218  	return vars.AddPreparedStmt(e.ID, preparedObj)
   219  }
   220  
   221  // InterDircuteInterDirc represents an EXECUTE interlock.
   222  // It cannot be executed by itself, all it needs to do is to build
   223  // another InterlockingDirectorate from a prepared memex.
   224  type InterDircuteInterDirc struct {
   225  	baseInterlockingDirectorate
   226  
   227  	is            schemareplicant.SchemaReplicant
   228  	name          string
   229  	usingVars     []memex.Expression
   230  	stmtInterDirc InterlockingDirectorate
   231  	stmt          ast.StmtNode
   232  	plan          causetembedded.Causet
   233  	id            uint32
   234  	lowerPriority bool
   235  	outputNames   []*types.FieldName
   236  }
   237  
   238  // Next implements the InterlockingDirectorate Next interface.
   239  func (e *InterDircuteInterDirc) Next(ctx context.Context, req *chunk.Chunk) error {
   240  	return nil
   241  }
   242  
   243  // Build builds a prepared memex into an interlock.
   244  // After Build, e.StmtInterDirc will be used to do the real execution.
   245  func (e *InterDircuteInterDirc) Build(b *interlockBuilder) error {
   246  	ok, err := causetembedded.IsPointGetWithPKOrUniqueKeyByAutoCommit(e.ctx, e.plan)
   247  	if err != nil {
   248  		return err
   249  	}
   250  	if ok {
   251  		err = e.ctx.InitTxnWithStartTS(math.MaxUint64)
   252  	}
   253  	if err != nil {
   254  		return err
   255  	}
   256  	stmtInterDirc := b.build(e.plan)
   257  	if b.err != nil {
   258  		log.Warn("rebuild plan in EXECUTE memex failed", zap.String("labelName of PREPARE memex", e.name))
   259  		return errors.Trace(b.err)
   260  	}
   261  	e.stmtInterDirc = stmtInterDirc
   262  	if e.ctx.GetStochastikVars().StmtCtx.Priority == allegrosql.NoPriority {
   263  		e.lowerPriority = needLowerPriority(e.plan)
   264  	}
   265  	return nil
   266  }
   267  
   268  // DeallocateInterDirc represent a DEALLOCATE interlock.
   269  type DeallocateInterDirc struct {
   270  	baseInterlockingDirectorate
   271  
   272  	Name string
   273  }
   274  
   275  // Next implements the InterlockingDirectorate Next interface.
   276  func (e *DeallocateInterDirc) Next(ctx context.Context, req *chunk.Chunk) error {
   277  	vars := e.ctx.GetStochastikVars()
   278  	id, ok := vars.PreparedStmtNameToID[e.Name]
   279  	if !ok {
   280  		return errors.Trace(causetembedded.ErrStmtNotFound)
   281  	}
   282  	preparedPointer := vars.PreparedStmts[id]
   283  	preparedObj, ok := preparedPointer.(*causetembedded.CachedPrepareStmt)
   284  	if !ok {
   285  		return errors.Errorf("invalid CachedPrepareStmt type")
   286  	}
   287  	prepared := preparedObj.PreparedAst
   288  	delete(vars.PreparedStmtNameToID, e.Name)
   289  	if causetembedded.PreparedCausetCacheEnabled() {
   290  		e.ctx.PreparedCausetCache().Delete(causetembedded.NewPSTMTCausetCacheKey(
   291  			vars, id, prepared.SchemaVersion,
   292  		))
   293  	}
   294  	vars.RemovePreparedStmt(id)
   295  	return nil
   296  }
   297  
   298  // CompileInterDircutePreparedStmt compiles a stochastik InterDircute command to a stmt.Statement.
   299  func CompileInterDircutePreparedStmt(ctx context.Context, sctx stochastikctx.Context,
   300  	ID uint32, args []types.Causet) (sqlexec.Statement, error) {
   301  	startTime := time.Now()
   302  	defer func() {
   303  		sctx.GetStochastikVars().DurationCompile = time.Since(startTime)
   304  	}()
   305  	execStmt := &ast.InterDircuteStmt{InterDircID: ID}
   306  	if err := ResetContextOfStmt(sctx, execStmt); err != nil {
   307  		return nil, err
   308  	}
   309  	execStmt.BinaryArgs = args
   310  	is := schemareplicant.GetSchemaReplicant(sctx)
   311  	execCauset, names, err := causet.Optimize(ctx, sctx, execStmt, is)
   312  	if err != nil {
   313  		return nil, err
   314  	}
   315  
   316  	stmt := &InterDircStmt{
   317  		GoCtx:           ctx,
   318  		SchemaReplicant: is,
   319  		Causet:          execCauset,
   320  		StmtNode:        execStmt,
   321  		Ctx:             sctx,
   322  		OutputNames:     names,
   323  	}
   324  	if preparedPointer, ok := sctx.GetStochastikVars().PreparedStmts[ID]; ok {
   325  		preparedObj, ok := preparedPointer.(*causetembedded.CachedPrepareStmt)
   326  		if !ok {
   327  			return nil, errors.Errorf("invalid CachedPrepareStmt type")
   328  		}
   329  		stmtCtx := sctx.GetStochastikVars().StmtCtx
   330  		stmt.Text = preparedObj.PreparedAst.Stmt.Text()
   331  		stmtCtx.OriginalALLEGROSQL = stmt.Text
   332  		stmtCtx.InitALLEGROSQLDigest(preparedObj.NormalizedALLEGROSQL, preparedObj.ALLEGROSQLDigest)
   333  	}
   334  	return stmt, nil
   335  }