github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/interlock/index_lookup_join.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  	"bytes"
    18  	"context"
    19  	"runtime"
    20  	"runtime/trace"
    21  	"sort"
    22  	"strconv"
    23  	"sync"
    24  	"sync/atomic"
    25  	"time"
    26  	"unsafe"
    27  
    28  	"github.com/whtcorpsinc/BerolinaSQL/allegrosql"
    29  	"github.com/whtcorpsinc/BerolinaSQL/terror"
    30  	"github.com/whtcorpsinc/errors"
    31  	causetembedded "github.com/whtcorpsinc/milevadb/causet/embedded"
    32  	"github.com/whtcorpsinc/milevadb/memex"
    33  	"github.com/whtcorpsinc/milevadb/soliton/chunk"
    34  	"github.com/whtcorpsinc/milevadb/soliton/codec"
    35  	"github.com/whtcorpsinc/milevadb/soliton/execdetails"
    36  	"github.com/whtcorpsinc/milevadb/soliton/logutil"
    37  	"github.com/whtcorpsinc/milevadb/soliton/memory"
    38  	"github.com/whtcorpsinc/milevadb/soliton/mvmap"
    39  	"github.com/whtcorpsinc/milevadb/soliton/ranger"
    40  	"github.com/whtcorpsinc/milevadb/stochastikctx"
    41  	"github.com/whtcorpsinc/milevadb/stochastikctx/stmtctx"
    42  	"github.com/whtcorpsinc/milevadb/types"
    43  	"go.uber.org/zap"
    44  )
    45  
    46  var _ InterlockingDirectorate = &IndexLookUpJoin{}
    47  
    48  // IndexLookUpJoin employs one outer worker and N innerWorkers to execute concurrently.
    49  // It preserves the order of the outer causet and support batch lookup.
    50  //
    51  // The execution flow is very similar to IndexLookUpReader:
    52  // 1. outerWorker read N outer rows, build a task and send it to result channel and inner worker channel.
    53  // 2. The innerWorker receives the task, builds key ranges from outer rows and fetch inner rows, builds inner event hash map.
    54  // 3. main thread receives the task, waits for inner worker finish handling the task.
    55  // 4. main thread join each outer event by look up the inner rows hash map in the task.
    56  type IndexLookUpJoin struct {
    57  	baseInterlockingDirectorate
    58  
    59  	resultCh   <-chan *lookUpJoinTask
    60  	cancelFunc context.CancelFunc
    61  	workerWg   *sync.WaitGroup
    62  
    63  	outerCtx outerCtx
    64  	innerCtx innerCtx
    65  
    66  	task       *lookUpJoinTask
    67  	joinResult *chunk.Chunk
    68  	innerIter  chunk.Iterator
    69  
    70  	joiner      joiner
    71  	isOuterJoin bool
    72  
    73  	requiredEvents int64
    74  
    75  	indexRanges   []*ranger.Range
    76  	keyOff2IdxOff []int
    77  	innerPtrBytes [][]byte
    78  
    79  	// lastDefCausHelper causetstore the information for last defCaus if there's complicated filter like defCaus > x_defCaus and defCaus < x_defCaus + 100.
    80  	lastDefCausHelper *causetembedded.DefCausWithCmpFuncManager
    81  
    82  	memTracker *memory.Tracker // track memory usage.
    83  
    84  	stats *indexLookUpJoinRuntimeStats
    85  }
    86  
    87  type outerCtx struct {
    88  	rowTypes    []*types.FieldType
    89  	keyDefCauss []int
    90  	filter      memex.CNFExprs
    91  }
    92  
    93  type innerCtx struct {
    94  	readerBuilder    *dataReaderBuilder
    95  	rowTypes         []*types.FieldType
    96  	keyDefCauss      []int
    97  	defCausLens      []int
    98  	hasPrefixDefCaus bool
    99  }
   100  
   101  type lookUpJoinTask struct {
   102  	outerResult *chunk.List
   103  	outerMatch  [][]bool
   104  
   105  	innerResult       *chunk.List
   106  	encodedLookUpKeys []*chunk.Chunk
   107  	lookupMap         *mvmap.MVMap
   108  	matchedInners     []chunk.Event
   109  
   110  	doneCh   chan error
   111  	cursor   chunk.EventPtr
   112  	hasMatch bool
   113  	hasNull  bool
   114  
   115  	memTracker *memory.Tracker // track memory usage.
   116  }
   117  
   118  type outerWorker struct {
   119  	outerCtx
   120  
   121  	lookup *IndexLookUpJoin
   122  
   123  	ctx       stochastikctx.Context
   124  	interlock InterlockingDirectorate
   125  
   126  	maxBatchSize int
   127  	batchSize    int
   128  
   129  	resultCh chan<- *lookUpJoinTask
   130  	innerCh  chan<- *lookUpJoinTask
   131  
   132  	parentMemTracker *memory.Tracker
   133  }
   134  
   135  type innerWorker struct {
   136  	innerCtx
   137  
   138  	taskCh       <-chan *lookUpJoinTask
   139  	outerCtx     outerCtx
   140  	ctx          stochastikctx.Context
   141  	interlockChk *chunk.Chunk
   142  
   143  	indexRanges               []*ranger.Range
   144  	nextDefCausCompareFilters *causetembedded.DefCausWithCmpFuncManager
   145  	keyOff2IdxOff             []int
   146  	stats                     *innerWorkerRuntimeStats
   147  }
   148  
   149  // Open implements the InterlockingDirectorate interface.
   150  func (e *IndexLookUpJoin) Open(ctx context.Context) error {
   151  	// Be careful, very dirty replog in this line!!!
   152  	// IndexLookUpJoin need to rebuild interlock (the dataReaderBuilder) during
   153  	// executing. However `interlock.Next()` is lazy evaluation when the RecordSet
   154  	// result is drained.
   155  	// Lazy evaluation means the saved stochastik context may change during interlock's
   156  	// building and its running.
   157  	// A specific sequence for example:
   158  	//
   159  	// e := buildInterlockingDirectorate()   // txn at build time
   160  	// recordSet := runStmt(e)
   161  	// stochastik.CommitTxn()    // txn closed
   162  	// recordSet.Next()
   163  	// e.dataReaderBuilder.Build() // txn is used again, which is already closed
   164  	//
   165  	// The trick here is `getSnapshotTS` will cache snapshot ts in the dataReaderBuilder,
   166  	// so even txn is destroyed later, the dataReaderBuilder could still use the
   167  	// cached snapshot ts to construct PosetDag.
   168  	_, err := e.innerCtx.readerBuilder.getSnapshotTS()
   169  	if err != nil {
   170  		return err
   171  	}
   172  
   173  	err = e.children[0].Open(ctx)
   174  	if err != nil {
   175  		return err
   176  	}
   177  	e.memTracker = memory.NewTracker(e.id, -1)
   178  	e.memTracker.AttachTo(e.ctx.GetStochastikVars().StmtCtx.MemTracker)
   179  	e.innerPtrBytes = make([][]byte, 0, 8)
   180  	if e.runtimeStats != nil {
   181  		e.stats = &indexLookUpJoinRuntimeStats{}
   182  		e.ctx.GetStochastikVars().StmtCtx.RuntimeStatsDefCausl.RegisterStats(e.id, e.stats)
   183  	}
   184  	e.startWorkers(ctx)
   185  	return nil
   186  }
   187  
   188  func (e *IndexLookUpJoin) startWorkers(ctx context.Context) {
   189  	concurrency := e.ctx.GetStochastikVars().IndexLookupJoinConcurrency()
   190  	if e.stats != nil {
   191  		e.stats.concurrency = concurrency
   192  	}
   193  	resultCh := make(chan *lookUpJoinTask, concurrency)
   194  	e.resultCh = resultCh
   195  	workerCtx, cancelFunc := context.WithCancel(ctx)
   196  	e.cancelFunc = cancelFunc
   197  	innerCh := make(chan *lookUpJoinTask, concurrency)
   198  	e.workerWg.Add(1)
   199  	go e.newOuterWorker(resultCh, innerCh).run(workerCtx, e.workerWg)
   200  	e.workerWg.Add(concurrency)
   201  	for i := 0; i < concurrency; i++ {
   202  		go e.newInnerWorker(innerCh).run(workerCtx, e.workerWg)
   203  	}
   204  }
   205  
   206  func (e *IndexLookUpJoin) newOuterWorker(resultCh, innerCh chan *lookUpJoinTask) *outerWorker {
   207  	ow := &outerWorker{
   208  		outerCtx:         e.outerCtx,
   209  		ctx:              e.ctx,
   210  		interlock:        e.children[0],
   211  		resultCh:         resultCh,
   212  		innerCh:          innerCh,
   213  		batchSize:        32,
   214  		maxBatchSize:     e.ctx.GetStochastikVars().IndexJoinBatchSize,
   215  		parentMemTracker: e.memTracker,
   216  		lookup:           e,
   217  	}
   218  	return ow
   219  }
   220  
   221  func (e *IndexLookUpJoin) newInnerWorker(taskCh chan *lookUpJoinTask) *innerWorker {
   222  	// Since multiple inner workers run concurrently, we should copy join's indexRanges for every worker to avoid data race.
   223  	copiedRanges := make([]*ranger.Range, 0, len(e.indexRanges))
   224  	for _, ran := range e.indexRanges {
   225  		copiedRanges = append(copiedRanges, ran.Clone())
   226  	}
   227  
   228  	var innerStats *innerWorkerRuntimeStats
   229  	if e.stats != nil {
   230  		innerStats = &e.stats.innerWorker
   231  	}
   232  	iw := &innerWorker{
   233  		innerCtx:      e.innerCtx,
   234  		outerCtx:      e.outerCtx,
   235  		taskCh:        taskCh,
   236  		ctx:           e.ctx,
   237  		interlockChk:  chunk.NewChunkWithCapacity(e.innerCtx.rowTypes, e.maxChunkSize),
   238  		indexRanges:   copiedRanges,
   239  		keyOff2IdxOff: e.keyOff2IdxOff,
   240  		stats:         innerStats,
   241  	}
   242  	if e.lastDefCausHelper != nil {
   243  		// nextCwf.TmpConstant needs to be reset for every individual
   244  		// inner worker to avoid data race when the inner workers is running
   245  		// concurrently.
   246  		nextCwf := *e.lastDefCausHelper
   247  		nextCwf.TmpConstant = make([]*memex.Constant, len(e.lastDefCausHelper.TmpConstant))
   248  		for i := range e.lastDefCausHelper.TmpConstant {
   249  			nextCwf.TmpConstant[i] = &memex.Constant{RetType: nextCwf.TargetDefCaus.RetType}
   250  		}
   251  		iw.nextDefCausCompareFilters = &nextCwf
   252  	}
   253  	return iw
   254  }
   255  
   256  // Next implements the InterlockingDirectorate interface.
   257  func (e *IndexLookUpJoin) Next(ctx context.Context, req *chunk.Chunk) error {
   258  	if e.isOuterJoin {
   259  		atomic.StoreInt64(&e.requiredEvents, int64(req.RequiredEvents()))
   260  	}
   261  	req.Reset()
   262  	e.joinResult.Reset()
   263  	for {
   264  		task, err := e.getFinishedTask(ctx)
   265  		if err != nil {
   266  			return err
   267  		}
   268  		if task == nil {
   269  			return nil
   270  		}
   271  		startTime := time.Now()
   272  		if e.innerIter == nil || e.innerIter.Current() == e.innerIter.End() {
   273  			e.lookUpMatchedInners(task, task.cursor)
   274  			e.innerIter = chunk.NewIterator4Slice(task.matchedInners)
   275  			e.innerIter.Begin()
   276  		}
   277  
   278  		outerEvent := task.outerResult.GetEvent(task.cursor)
   279  		if e.innerIter.Current() != e.innerIter.End() {
   280  			matched, isNull, err := e.joiner.tryToMatchInners(outerEvent, e.innerIter, req)
   281  			if err != nil {
   282  				return err
   283  			}
   284  			task.hasMatch = task.hasMatch || matched
   285  			task.hasNull = task.hasNull || isNull
   286  		}
   287  		if e.innerIter.Current() == e.innerIter.End() {
   288  			if !task.hasMatch {
   289  				e.joiner.onMissMatch(task.hasNull, outerEvent, req)
   290  			}
   291  			task.cursor.EventIdx++
   292  			if int(task.cursor.EventIdx) == task.outerResult.GetChunk(int(task.cursor.ChkIdx)).NumEvents() {
   293  				task.cursor.ChkIdx++
   294  				task.cursor.EventIdx = 0
   295  			}
   296  			task.hasMatch = false
   297  			task.hasNull = false
   298  		}
   299  		if e.stats != nil {
   300  			atomic.AddInt64(&e.stats.probe, int64(time.Since(startTime)))
   301  		}
   302  		if req.IsFull() {
   303  			return nil
   304  		}
   305  	}
   306  }
   307  
   308  func (e *IndexLookUpJoin) getFinishedTask(ctx context.Context) (*lookUpJoinTask, error) {
   309  	task := e.task
   310  	if task != nil && int(task.cursor.ChkIdx) < task.outerResult.NumChunks() {
   311  		return task, nil
   312  	}
   313  
   314  	select {
   315  	case task = <-e.resultCh:
   316  	case <-ctx.Done():
   317  		return nil, ctx.Err()
   318  	}
   319  	if task == nil {
   320  		return nil, nil
   321  	}
   322  
   323  	select {
   324  	case err := <-task.doneCh:
   325  		if err != nil {
   326  			return nil, err
   327  		}
   328  	case <-ctx.Done():
   329  		return nil, ctx.Err()
   330  	}
   331  
   332  	e.task = task
   333  	return task, nil
   334  }
   335  
   336  func (e *IndexLookUpJoin) lookUpMatchedInners(task *lookUpJoinTask, rowPtr chunk.EventPtr) {
   337  	outerKey := task.encodedLookUpKeys[rowPtr.ChkIdx].GetEvent(int(rowPtr.EventIdx)).GetBytes(0)
   338  	e.innerPtrBytes = task.lookupMap.Get(outerKey, e.innerPtrBytes[:0])
   339  	task.matchedInners = task.matchedInners[:0]
   340  
   341  	for _, b := range e.innerPtrBytes {
   342  		ptr := *(*chunk.EventPtr)(unsafe.Pointer(&b[0]))
   343  		matchedInner := task.innerResult.GetEvent(ptr)
   344  		task.matchedInners = append(task.matchedInners, matchedInner)
   345  	}
   346  }
   347  
   348  func (ow *outerWorker) run(ctx context.Context, wg *sync.WaitGroup) {
   349  	defer trace.StartRegion(ctx, "IndexLookupJoinOuterWorker").End()
   350  	defer func() {
   351  		if r := recover(); r != nil {
   352  			buf := make([]byte, 4096)
   353  			stackSize := runtime.Stack(buf, false)
   354  			buf = buf[:stackSize]
   355  			logutil.Logger(ctx).Error("outerWorker panicked", zap.String("stack", string(buf)))
   356  			task := &lookUpJoinTask{doneCh: make(chan error, 1)}
   357  			task.doneCh <- errors.Errorf("%v", r)
   358  			ow.pushToChan(ctx, task, ow.resultCh)
   359  		}
   360  		close(ow.resultCh)
   361  		close(ow.innerCh)
   362  		wg.Done()
   363  	}()
   364  	for {
   365  		task, err := ow.buildTask(ctx)
   366  		if err != nil {
   367  			task.doneCh <- err
   368  			ow.pushToChan(ctx, task, ow.resultCh)
   369  			return
   370  		}
   371  		if task == nil {
   372  			return
   373  		}
   374  
   375  		if finished := ow.pushToChan(ctx, task, ow.innerCh); finished {
   376  			return
   377  		}
   378  
   379  		if finished := ow.pushToChan(ctx, task, ow.resultCh); finished {
   380  			return
   381  		}
   382  	}
   383  }
   384  
   385  func (ow *outerWorker) pushToChan(ctx context.Context, task *lookUpJoinTask, dst chan<- *lookUpJoinTask) bool {
   386  	select {
   387  	case <-ctx.Done():
   388  		return true
   389  	case dst <- task:
   390  	}
   391  	return false
   392  }
   393  
   394  // buildTask builds a lookUpJoinTask and read outer rows.
   395  // When err is not nil, task must not be nil to send the error to the main thread via task.
   396  func (ow *outerWorker) buildTask(ctx context.Context) (*lookUpJoinTask, error) {
   397  	task := &lookUpJoinTask{
   398  		doneCh:      make(chan error, 1),
   399  		outerResult: newList(ow.interlock),
   400  		lookupMap:   mvmap.NewMVMap(),
   401  	}
   402  	task.memTracker = memory.NewTracker(-1, -1)
   403  	task.outerResult.GetMemTracker().AttachTo(task.memTracker)
   404  	task.memTracker.AttachTo(ow.parentMemTracker)
   405  
   406  	ow.increaseBatchSize()
   407  	requiredEvents := ow.batchSize
   408  	if ow.lookup.isOuterJoin {
   409  		// If it is outerJoin, push the requiredEvents down.
   410  		// Note: buildTask is triggered when `Open` is called, but
   411  		// ow.lookup.requiredEvents is set when `Next` is called. Thus we check
   412  		// whether it's 0 here.
   413  		if parentRequired := int(atomic.LoadInt64(&ow.lookup.requiredEvents)); parentRequired != 0 {
   414  			requiredEvents = parentRequired
   415  		}
   416  	}
   417  	maxChunkSize := ow.ctx.GetStochastikVars().MaxChunkSize
   418  	for requiredEvents > task.outerResult.Len() {
   419  		chk := chunk.NewChunkWithCapacity(ow.outerCtx.rowTypes, maxChunkSize)
   420  		chk = chk.SetRequiredEvents(requiredEvents, maxChunkSize)
   421  		err := Next(ctx, ow.interlock, chk)
   422  		if err != nil {
   423  			return task, err
   424  		}
   425  		if chk.NumEvents() == 0 {
   426  			break
   427  		}
   428  
   429  		task.outerResult.Add(chk)
   430  	}
   431  	if task.outerResult.Len() == 0 {
   432  		return nil, nil
   433  	}
   434  	numChks := task.outerResult.NumChunks()
   435  	if ow.filter != nil {
   436  		task.outerMatch = make([][]bool, task.outerResult.NumChunks())
   437  		var err error
   438  		for i := 0; i < numChks; i++ {
   439  			chk := task.outerResult.GetChunk(i)
   440  			outerMatch := make([]bool, 0, chk.NumEvents())
   441  			task.memTracker.Consume(int64(cap(outerMatch)))
   442  			task.outerMatch[i], err = memex.VectorizedFilter(ow.ctx, ow.filter, chunk.NewIterator4Chunk(chk), outerMatch)
   443  			if err != nil {
   444  				return task, err
   445  			}
   446  		}
   447  	}
   448  	task.encodedLookUpKeys = make([]*chunk.Chunk, task.outerResult.NumChunks())
   449  	for i := range task.encodedLookUpKeys {
   450  		task.encodedLookUpKeys[i] = chunk.NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(allegrosql.TypeBlob)}, task.outerResult.GetChunk(i).NumEvents())
   451  	}
   452  	return task, nil
   453  }
   454  
   455  func (ow *outerWorker) increaseBatchSize() {
   456  	if ow.batchSize < ow.maxBatchSize {
   457  		ow.batchSize *= 2
   458  	}
   459  	if ow.batchSize > ow.maxBatchSize {
   460  		ow.batchSize = ow.maxBatchSize
   461  	}
   462  }
   463  
   464  func (iw *innerWorker) run(ctx context.Context, wg *sync.WaitGroup) {
   465  	defer trace.StartRegion(ctx, "IndexLookupJoinInnerWorker").End()
   466  	var task *lookUpJoinTask
   467  	defer func() {
   468  		if r := recover(); r != nil {
   469  			buf := make([]byte, 4096)
   470  			stackSize := runtime.Stack(buf, false)
   471  			buf = buf[:stackSize]
   472  			logutil.Logger(ctx).Error("innerWorker panicked", zap.String("stack", string(buf)))
   473  			// "task != nil" is guaranteed when panic happened.
   474  			task.doneCh <- errors.Errorf("%v", r)
   475  		}
   476  		wg.Done()
   477  	}()
   478  
   479  	for ok := true; ok; {
   480  		select {
   481  		case task, ok = <-iw.taskCh:
   482  			if !ok {
   483  				return
   484  			}
   485  		case <-ctx.Done():
   486  			return
   487  		}
   488  
   489  		err := iw.handleTask(ctx, task)
   490  		task.doneCh <- err
   491  	}
   492  }
   493  
   494  type indexJoinLookUpContent struct {
   495  	keys  []types.Causet
   496  	event chunk.Event
   497  }
   498  
   499  func (iw *innerWorker) handleTask(ctx context.Context, task *lookUpJoinTask) error {
   500  	if iw.stats != nil {
   501  		start := time.Now()
   502  		defer func() {
   503  			atomic.AddInt64(&iw.stats.totalTime, int64(time.Since(start)))
   504  		}()
   505  	}
   506  	lookUpContents, err := iw.constructLookupContent(task)
   507  	if err != nil {
   508  		return err
   509  	}
   510  	err = iw.fetchInnerResults(ctx, task, lookUpContents)
   511  	if err != nil {
   512  		return err
   513  	}
   514  	err = iw.buildLookUpMap(task)
   515  	if err != nil {
   516  		return err
   517  	}
   518  	return nil
   519  }
   520  
   521  func (iw *innerWorker) constructLookupContent(task *lookUpJoinTask) ([]*indexJoinLookUpContent, error) {
   522  	if iw.stats != nil {
   523  		start := time.Now()
   524  		defer func() {
   525  			atomic.AddInt64(&iw.stats.task, 1)
   526  			atomic.AddInt64(&iw.stats.construct, int64(time.Since(start)))
   527  		}()
   528  	}
   529  	lookUpContents := make([]*indexJoinLookUpContent, 0, task.outerResult.Len())
   530  	keyBuf := make([]byte, 0, 64)
   531  	for chkIdx := 0; chkIdx < task.outerResult.NumChunks(); chkIdx++ {
   532  		chk := task.outerResult.GetChunk(chkIdx)
   533  		numEvents := chk.NumEvents()
   534  		for rowIdx := 0; rowIdx < numEvents; rowIdx++ {
   535  			dLookUpKey, err := iw.constructCausetLookupKey(task, chkIdx, rowIdx)
   536  			if err != nil {
   537  				return nil, err
   538  			}
   539  			if dLookUpKey == nil {
   540  				// Append null to make looUpKeys the same length as outer Result.
   541  				task.encodedLookUpKeys[chkIdx].AppendNull(0)
   542  				continue
   543  			}
   544  			keyBuf = keyBuf[:0]
   545  			keyBuf, err = codec.EncodeKey(iw.ctx.GetStochastikVars().StmtCtx, keyBuf, dLookUpKey...)
   546  			if err != nil {
   547  				return nil, err
   548  			}
   549  			// CausetStore the encoded lookup key in chunk, so we can use it to lookup the matched inners directly.
   550  			task.encodedLookUpKeys[chkIdx].AppendBytes(0, keyBuf)
   551  			if iw.hasPrefixDefCaus {
   552  				for i := range iw.outerCtx.keyDefCauss {
   553  					// If it's a prefix defCausumn. Try to fix it.
   554  					if iw.defCausLens[i] != types.UnspecifiedLength {
   555  						ranger.CutCausetByPrefixLen(&dLookUpKey[i], iw.defCausLens[i], iw.rowTypes[iw.keyDefCauss[i]])
   556  					}
   557  				}
   558  				// dLookUpKey is sorted and deduplicated at sortAndDedupLookUpContents.
   559  				// So we don't need to do it here.
   560  			}
   561  			lookUpContents = append(lookUpContents, &indexJoinLookUpContent{keys: dLookUpKey, event: chk.GetEvent(rowIdx)})
   562  		}
   563  	}
   564  
   565  	for i := range task.encodedLookUpKeys {
   566  		task.memTracker.Consume(task.encodedLookUpKeys[i].MemoryUsage())
   567  	}
   568  	lookUpContents = iw.sortAndDedupLookUpContents(lookUpContents)
   569  	return lookUpContents, nil
   570  }
   571  
   572  func (iw *innerWorker) constructCausetLookupKey(task *lookUpJoinTask, chkIdx, rowIdx int) ([]types.Causet, error) {
   573  	if task.outerMatch != nil && !task.outerMatch[chkIdx][rowIdx] {
   574  		return nil, nil
   575  	}
   576  	outerEvent := task.outerResult.GetChunk(chkIdx).GetEvent(rowIdx)
   577  	sc := iw.ctx.GetStochastikVars().StmtCtx
   578  	keyLen := len(iw.keyDefCauss)
   579  	dLookupKey := make([]types.Causet, 0, keyLen)
   580  	for i, keyDefCaus := range iw.outerCtx.keyDefCauss {
   581  		outerValue := outerEvent.GetCauset(keyDefCaus, iw.outerCtx.rowTypes[keyDefCaus])
   582  		// Join-on-condition can be promised to be equal-condition in
   583  		// IndexNestedLoopJoin, thus the filter will always be false if
   584  		// outerValue is null, and we don't need to lookup it.
   585  		if outerValue.IsNull() {
   586  			return nil, nil
   587  		}
   588  		innerDefCausType := iw.rowTypes[iw.keyDefCauss[i]]
   589  		innerValue, err := outerValue.ConvertTo(sc, innerDefCausType)
   590  		if err != nil {
   591  			// If the converted outerValue overflows, we don't need to lookup it.
   592  			if terror.ErrorEqual(err, types.ErrOverflow) {
   593  				return nil, nil
   594  			}
   595  			if terror.ErrorEqual(err, types.ErrTruncated) && (innerDefCausType.Tp == allegrosql.TypeSet || innerDefCausType.Tp == allegrosql.TypeEnum) {
   596  				return nil, nil
   597  			}
   598  			return nil, err
   599  		}
   600  		cmp, err := outerValue.CompareCauset(sc, &innerValue)
   601  		if err != nil {
   602  			return nil, err
   603  		}
   604  		if cmp != 0 {
   605  			// If the converted outerValue is not equal to the origin outerValue, we don't need to lookup it.
   606  			return nil, nil
   607  		}
   608  		dLookupKey = append(dLookupKey, innerValue)
   609  	}
   610  	return dLookupKey, nil
   611  }
   612  
   613  func (iw *innerWorker) sortAndDedupLookUpContents(lookUpContents []*indexJoinLookUpContent) []*indexJoinLookUpContent {
   614  	if len(lookUpContents) < 2 {
   615  		return lookUpContents
   616  	}
   617  	sc := iw.ctx.GetStochastikVars().StmtCtx
   618  	sort.Slice(lookUpContents, func(i, j int) bool {
   619  		cmp := compareEvent(sc, lookUpContents[i].keys, lookUpContents[j].keys)
   620  		if cmp != 0 || iw.nextDefCausCompareFilters == nil {
   621  			return cmp < 0
   622  		}
   623  		return iw.nextDefCausCompareFilters.CompareEvent(lookUpContents[i].event, lookUpContents[j].event) < 0
   624  	})
   625  	deDupedLookupKeys := lookUpContents[:1]
   626  	for i := 1; i < len(lookUpContents); i++ {
   627  		cmp := compareEvent(sc, lookUpContents[i].keys, lookUpContents[i-1].keys)
   628  		if cmp != 0 || (iw.nextDefCausCompareFilters != nil && iw.nextDefCausCompareFilters.CompareEvent(lookUpContents[i].event, lookUpContents[i-1].event) != 0) {
   629  			deDupedLookupKeys = append(deDupedLookupKeys, lookUpContents[i])
   630  		}
   631  	}
   632  	return deDupedLookupKeys
   633  }
   634  
   635  func compareEvent(sc *stmtctx.StatementContext, left, right []types.Causet) int {
   636  	for idx := 0; idx < len(left); idx++ {
   637  		cmp, err := left[idx].CompareCauset(sc, &right[idx])
   638  		// We only compare rows with the same type, no error to return.
   639  		terror.Log(err)
   640  		if cmp > 0 {
   641  			return 1
   642  		} else if cmp < 0 {
   643  			return -1
   644  		}
   645  	}
   646  	return 0
   647  }
   648  
   649  func (iw *innerWorker) fetchInnerResults(ctx context.Context, task *lookUpJoinTask, lookUpContent []*indexJoinLookUpContent) error {
   650  	if iw.stats != nil {
   651  		start := time.Now()
   652  		defer func() {
   653  			atomic.AddInt64(&iw.stats.fetch, int64(time.Since(start)))
   654  		}()
   655  	}
   656  	innerInterDirc, err := iw.readerBuilder.buildInterlockingDirectorateForIndexJoin(ctx, lookUpContent, iw.indexRanges, iw.keyOff2IdxOff, iw.nextDefCausCompareFilters)
   657  	if err != nil {
   658  		return err
   659  	}
   660  	defer terror.Call(innerInterDirc.Close)
   661  	innerResult := chunk.NewList(retTypes(innerInterDirc), iw.ctx.GetStochastikVars().MaxChunkSize, iw.ctx.GetStochastikVars().MaxChunkSize)
   662  	innerResult.GetMemTracker().SetLabel(memory.LabelForBuildSideResult)
   663  	innerResult.GetMemTracker().AttachTo(task.memTracker)
   664  	for {
   665  		select {
   666  		case <-ctx.Done():
   667  			return ctx.Err()
   668  		default:
   669  		}
   670  		err := Next(ctx, innerInterDirc, iw.interlockChk)
   671  		if err != nil {
   672  			return err
   673  		}
   674  		if iw.interlockChk.NumEvents() == 0 {
   675  			break
   676  		}
   677  		innerResult.Add(iw.interlockChk)
   678  		iw.interlockChk = newFirstChunk(innerInterDirc)
   679  	}
   680  	task.innerResult = innerResult
   681  	return nil
   682  }
   683  
   684  func (iw *innerWorker) buildLookUpMap(task *lookUpJoinTask) error {
   685  	if iw.stats != nil {
   686  		start := time.Now()
   687  		defer func() {
   688  			atomic.AddInt64(&iw.stats.build, int64(time.Since(start)))
   689  		}()
   690  	}
   691  	keyBuf := make([]byte, 0, 64)
   692  	valBuf := make([]byte, 8)
   693  	for i := 0; i < task.innerResult.NumChunks(); i++ {
   694  		chk := task.innerResult.GetChunk(i)
   695  		for j := 0; j < chk.NumEvents(); j++ {
   696  			innerEvent := chk.GetEvent(j)
   697  			if iw.hasNullInJoinKey(innerEvent) {
   698  				continue
   699  			}
   700  
   701  			keyBuf = keyBuf[:0]
   702  			for _, keyDefCaus := range iw.keyDefCauss {
   703  				d := innerEvent.GetCauset(keyDefCaus, iw.rowTypes[keyDefCaus])
   704  				var err error
   705  				keyBuf, err = codec.EncodeKey(iw.ctx.GetStochastikVars().StmtCtx, keyBuf, d)
   706  				if err != nil {
   707  					return err
   708  				}
   709  			}
   710  			rowPtr := chunk.EventPtr{ChkIdx: uint32(i), EventIdx: uint32(j)}
   711  			*(*chunk.EventPtr)(unsafe.Pointer(&valBuf[0])) = rowPtr
   712  			task.lookupMap.Put(keyBuf, valBuf)
   713  		}
   714  	}
   715  	return nil
   716  }
   717  
   718  func (iw *innerWorker) hasNullInJoinKey(event chunk.Event) bool {
   719  	for _, ordinal := range iw.keyDefCauss {
   720  		if event.IsNull(ordinal) {
   721  			return true
   722  		}
   723  	}
   724  	return false
   725  }
   726  
   727  // Close implements the InterlockingDirectorate interface.
   728  func (e *IndexLookUpJoin) Close() error {
   729  	if e.cancelFunc != nil {
   730  		e.cancelFunc()
   731  	}
   732  	e.workerWg.Wait()
   733  	e.memTracker = nil
   734  	e.task = nil
   735  	return e.baseInterlockingDirectorate.Close()
   736  }
   737  
   738  type indexLookUpJoinRuntimeStats struct {
   739  	concurrency int
   740  	probe       int64
   741  	innerWorker innerWorkerRuntimeStats
   742  }
   743  
   744  type innerWorkerRuntimeStats struct {
   745  	totalTime int64
   746  	task      int64
   747  	construct int64
   748  	fetch     int64
   749  	build     int64
   750  	join      int64
   751  }
   752  
   753  func (e *indexLookUpJoinRuntimeStats) String() string {
   754  	buf := bytes.NewBuffer(make([]byte, 0, 16))
   755  	if e.innerWorker.totalTime > 0 {
   756  		buf.WriteString("inner:{total:")
   757  		buf.WriteString(time.Duration(e.innerWorker.totalTime).String())
   758  		buf.WriteString(", concurrency:")
   759  		if e.concurrency > 0 {
   760  			buf.WriteString(strconv.Itoa(e.concurrency))
   761  		} else {
   762  			buf.WriteString("OFF")
   763  		}
   764  		buf.WriteString(", task:")
   765  		buf.WriteString(strconv.FormatInt(e.innerWorker.task, 10))
   766  		buf.WriteString(", construct:")
   767  		buf.WriteString(time.Duration(e.innerWorker.construct).String())
   768  		buf.WriteString(", fetch:")
   769  		buf.WriteString(time.Duration(e.innerWorker.fetch).String())
   770  		buf.WriteString(", build:")
   771  		buf.WriteString(time.Duration(e.innerWorker.build).String())
   772  		if e.innerWorker.join > 0 {
   773  			buf.WriteString(", join:")
   774  			buf.WriteString(time.Duration(e.innerWorker.join).String())
   775  		}
   776  		buf.WriteString("}")
   777  	}
   778  	if e.probe > 0 {
   779  		buf.WriteString(", probe:")
   780  		buf.WriteString(time.Duration(e.probe).String())
   781  	}
   782  	return buf.String()
   783  }
   784  
   785  func (e *indexLookUpJoinRuntimeStats) Clone() execdetails.RuntimeStats {
   786  	return &indexLookUpJoinRuntimeStats{
   787  		concurrency: e.concurrency,
   788  		probe:       e.probe,
   789  		innerWorker: e.innerWorker,
   790  	}
   791  }
   792  
   793  func (e *indexLookUpJoinRuntimeStats) Merge(rs execdetails.RuntimeStats) {
   794  	tmp, ok := rs.(*indexLookUpJoinRuntimeStats)
   795  	if !ok {
   796  		return
   797  	}
   798  	e.probe += tmp.probe
   799  	e.innerWorker.totalTime += tmp.innerWorker.totalTime
   800  	e.innerWorker.task += tmp.innerWorker.task
   801  	e.innerWorker.construct += tmp.innerWorker.construct
   802  	e.innerWorker.fetch += tmp.innerWorker.fetch
   803  	e.innerWorker.build += tmp.innerWorker.build
   804  	e.innerWorker.join += tmp.innerWorker.join
   805  }
   806  
   807  // Tp implements the RuntimeStats interface.
   808  func (e *indexLookUpJoinRuntimeStats) Tp() int {
   809  	return execdetails.TpIndexLookUpJoinRuntimeStats
   810  }