github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/interlock/replace.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  	"fmt"
    19  	"runtime/trace"
    20  
    21  	"github.com/whtcorpsinc/BerolinaSQL/allegrosql"
    22  	"github.com/whtcorpsinc/errors"
    23  	"github.com/whtcorpsinc/milevadb/blockcodec"
    24  	"github.com/whtcorpsinc/milevadb/ekv"
    25  	"github.com/whtcorpsinc/milevadb/soliton/chunk"
    26  	"github.com/whtcorpsinc/milevadb/soliton/logutil"
    27  	"github.com/whtcorpsinc/milevadb/soliton/memory"
    28  	"github.com/whtcorpsinc/milevadb/types"
    29  	"go.uber.org/zap"
    30  )
    31  
    32  // ReplaceInterDirc represents a replace interlock.
    33  type ReplaceInterDirc struct {
    34  	*InsertValues
    35  	Priority int
    36  }
    37  
    38  // Close implements the InterlockingDirectorate Close interface.
    39  func (e *ReplaceInterDirc) Close() error {
    40  	e.setMessage()
    41  	if e.SelectInterDirc != nil {
    42  		return e.SelectInterDirc.Close()
    43  	}
    44  	return nil
    45  }
    46  
    47  // Open implements the InterlockingDirectorate Open interface.
    48  func (e *ReplaceInterDirc) Open(ctx context.Context) error {
    49  	e.memTracker = memory.NewTracker(e.id, -1)
    50  	e.memTracker.AttachTo(e.ctx.GetStochastikVars().StmtCtx.MemTracker)
    51  
    52  	if e.SelectInterDirc != nil {
    53  		return e.SelectInterDirc.Open(ctx)
    54  	}
    55  	e.initEvalBuffer()
    56  	return nil
    57  }
    58  
    59  // removeEvent removes the duplicate event and cleanup its keys in the key-value map,
    60  // but if the to-be-removed event equals to the to-be-added event, no remove or add things to do.
    61  func (e *ReplaceInterDirc) removeEvent(ctx context.Context, txn ekv.Transaction, handle ekv.Handle, r toBeCheckedEvent) (bool, error) {
    62  	newEvent := r.event
    63  	oldEvent, err := getOldEvent(ctx, e.ctx, txn, r.t, handle, e.GenExprs)
    64  	if err != nil {
    65  		logutil.BgLogger().Error("get old event failed when replace",
    66  			zap.String("handle", handle.String()),
    67  			zap.String("toBeInsertedEvent", types.CausetsToStrNoErr(r.event)))
    68  		if ekv.IsErrNotFound(err) {
    69  			err = errors.NotFoundf("can not be duplicated event, due to old event not found. handle %s", handle)
    70  		}
    71  		return false, err
    72  	}
    73  
    74  	rowUnchanged, err := types.EqualCausets(e.ctx.GetStochastikVars().StmtCtx, oldEvent, newEvent)
    75  	if err != nil {
    76  		return false, err
    77  	}
    78  	if rowUnchanged {
    79  		e.ctx.GetStochastikVars().StmtCtx.AddAffectedEvents(1)
    80  		return true, nil
    81  	}
    82  
    83  	err = r.t.RemoveRecord(e.ctx, handle, oldEvent)
    84  	if err != nil {
    85  		return false, err
    86  	}
    87  	e.ctx.GetStochastikVars().StmtCtx.AddAffectedEvents(1)
    88  	return false, nil
    89  }
    90  
    91  // replaceEvent removes all duplicate rows for one event, then inserts it.
    92  func (e *ReplaceInterDirc) replaceEvent(ctx context.Context, r toBeCheckedEvent) error {
    93  	txn, err := e.ctx.Txn(true)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	if r.handleKey != nil {
    99  		handle, err := blockcodec.DecodeEventKey(r.handleKey.newKey)
   100  		if err != nil {
   101  			return err
   102  		}
   103  
   104  		if _, err := txn.Get(ctx, r.handleKey.newKey); err == nil {
   105  			rowUnchanged, err := e.removeEvent(ctx, txn, handle, r)
   106  			if err != nil {
   107  				return err
   108  			}
   109  			if rowUnchanged {
   110  				return nil
   111  			}
   112  		} else {
   113  			if !ekv.IsErrNotFound(err) {
   114  				return err
   115  			}
   116  		}
   117  	}
   118  
   119  	// Keep on removing duplicated rows.
   120  	for {
   121  		rowUnchanged, foundDupKey, err := e.removeIndexEvent(ctx, txn, r)
   122  		if err != nil {
   123  			return err
   124  		}
   125  		if rowUnchanged {
   126  			return nil
   127  		}
   128  		if foundDupKey {
   129  			continue
   130  		}
   131  		break
   132  	}
   133  
   134  	// No duplicated rows now, insert the event.
   135  	err = e.addRecord(ctx, r.event)
   136  	if err != nil {
   137  		return err
   138  	}
   139  	return nil
   140  }
   141  
   142  // removeIndexEvent removes the event which has a duplicated key.
   143  // the return values:
   144  //     1. bool: true when the event is unchanged. This means no need to remove, and then add the event.
   145  //     2. bool: true when found the duplicated key. This only means that duplicated key was found,
   146  //              and the event was removed.
   147  //     3. error: the error.
   148  func (e *ReplaceInterDirc) removeIndexEvent(ctx context.Context, txn ekv.Transaction, r toBeCheckedEvent) (bool, bool, error) {
   149  	for _, uk := range r.uniqueKeys {
   150  		val, err := txn.Get(ctx, uk.newKey)
   151  		if err != nil {
   152  			if ekv.IsErrNotFound(err) {
   153  				continue
   154  			}
   155  			return false, false, err
   156  		}
   157  		handle, err := blockcodec.DecodeHandleInUniqueIndexValue(val, uk.commonHandle)
   158  		if err != nil {
   159  			return false, true, err
   160  		}
   161  		rowUnchanged, err := e.removeEvent(ctx, txn, handle, r)
   162  		if err != nil {
   163  			return false, true, err
   164  		}
   165  		return rowUnchanged, true, nil
   166  	}
   167  	return false, false, nil
   168  }
   169  
   170  func (e *ReplaceInterDirc) exec(ctx context.Context, newEvents [][]types.Causet) error {
   171  	/*
   172  	 * MyALLEGROSQL uses the following algorithm for REPLACE (and LOAD DATA ... REPLACE):
   173  	 *  1. Try to insert the new event into the causet
   174  	 *  2. While the insertion fails because a duplicate-key error occurs for a primary key or unique index:
   175  	 *  3. Delete from the causet the conflicting event that has the duplicate key value
   176  	 *  4. Try again to insert the new event into the causet
   177  	 * See http://dev.allegrosql.com/doc/refman/5.7/en/replace.html
   178  	 *
   179  	 * For REPLACE memexs, the affected-rows value is 2 if the new event replaced an old event,
   180  	 * because in this case, one event was inserted after the duplicate was deleted.
   181  	 * See http://dev.allegrosql.com/doc/refman/5.7/en/allegrosql-affected-rows.html
   182  	 */
   183  
   184  	defer trace.StartRegion(ctx, "ReplaceInterDirc").End()
   185  	// Get keys need to be checked.
   186  	toBeCheckedEvents, err := getKeysNeedCheck(ctx, e.ctx, e.Block, newEvents)
   187  	if err != nil {
   188  		return err
   189  	}
   190  
   191  	txn, err := e.ctx.Txn(true)
   192  	if err != nil {
   193  		return err
   194  	}
   195  	txnSize := txn.Size()
   196  
   197  	if e.defCauslectRuntimeStatsEnabled() {
   198  		if snapshot := txn.GetSnapshot(); snapshot != nil {
   199  			snapshot.SetOption(ekv.DefCauslectRuntimeStats, e.stats.SnapshotRuntimeStats)
   200  			defer snapshot.DelOption(ekv.DefCauslectRuntimeStats)
   201  		}
   202  	}
   203  
   204  	// Use BatchGet to fill cache.
   205  	// It's an optimization and could be removed without affecting correctness.
   206  	if err = prefetchDataCache(ctx, txn, toBeCheckedEvents); err != nil {
   207  		return err
   208  	}
   209  
   210  	e.ctx.GetStochastikVars().StmtCtx.AddRecordEvents(uint64(len(newEvents)))
   211  	for _, r := range toBeCheckedEvents {
   212  		err = e.replaceEvent(ctx, r)
   213  		if err != nil {
   214  			return err
   215  		}
   216  	}
   217  	e.memTracker.Consume(int64(txn.Size() - txnSize))
   218  	return nil
   219  }
   220  
   221  // Next implements the InterlockingDirectorate Next interface.
   222  func (e *ReplaceInterDirc) Next(ctx context.Context, req *chunk.Chunk) error {
   223  	req.Reset()
   224  	if len(e.children) > 0 && e.children[0] != nil {
   225  		return insertEventsFromSelect(ctx, e)
   226  	}
   227  	return insertEvents(ctx, e)
   228  }
   229  
   230  // setMessage sets info message(ERR_INSERT_INFO) generated by REPLACE memex
   231  func (e *ReplaceInterDirc) setMessage() {
   232  	stmtCtx := e.ctx.GetStochastikVars().StmtCtx
   233  	numRecords := stmtCtx.RecordEvents()
   234  	if e.SelectInterDirc != nil || numRecords > 1 {
   235  		numWarnings := stmtCtx.WarningCount()
   236  		numDuplicates := stmtCtx.AffectedEvents() - numRecords
   237  		msg := fmt.Sprintf(allegrosql.MyALLEGROSQLErrName[allegrosql.ErrInsertInfo], numRecords, numDuplicates, numWarnings)
   238  		stmtCtx.SetMessage(msg)
   239  	}
   240  }