github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/colexec/relative_rank_tmpl.go (about)

     1  // Copyright 2020 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  // {{/*
    12  // +build execgen_template
    13  //
    14  // This file is the execgen template for relative_rank.eg.go. It's formatted in
    15  // a special way, so it's both valid Go and a valid text/template input. This
    16  // permits editing this file with editor support.
    17  //
    18  // */}}
    19  
    20  package colexec
    21  
    22  import (
    23  	"context"
    24  
    25  	"github.com/cockroachdb/cockroach/pkg/col/coldata"
    26  	"github.com/cockroachdb/cockroach/pkg/sql/colcontainer"
    27  	"github.com/cockroachdb/cockroach/pkg/sql/colexecbase"
    28  	"github.com/cockroachdb/cockroach/pkg/sql/colexecbase/colexecerror"
    29  	"github.com/cockroachdb/cockroach/pkg/sql/colmem"
    30  	"github.com/cockroachdb/cockroach/pkg/sql/execinfrapb"
    31  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    32  	"github.com/cockroachdb/cockroach/pkg/util/mon"
    33  	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
    34  	"github.com/cockroachdb/errors"
    35  	"github.com/marusama/semaphore"
    36  )
    37  
    38  // TODO(yuzefovich): add benchmarks.
    39  
    40  // NewRelativeRankOperator creates a new Operator that computes window
    41  // functions PERCENT_RANK or CUME_DIST (depending on the passed in windowFn).
    42  // outputColIdx specifies in which coldata.Vec the operator should put its
    43  // output (if there is no such column, a new column is appended).
    44  func NewRelativeRankOperator(
    45  	unlimitedAllocator *colmem.Allocator,
    46  	memoryLimit int64,
    47  	diskQueueCfg colcontainer.DiskQueueCfg,
    48  	fdSemaphore semaphore.Semaphore,
    49  	input colexecbase.Operator,
    50  	inputTypes []*types.T,
    51  	windowFn execinfrapb.WindowerSpec_WindowFunc,
    52  	orderingCols []execinfrapb.Ordering_Column,
    53  	outputColIdx int,
    54  	partitionColIdx int,
    55  	peersColIdx int,
    56  	diskAcc *mon.BoundAccount,
    57  ) (colexecbase.Operator, error) {
    58  	if len(orderingCols) == 0 {
    59  		constValue := float64(0)
    60  		if windowFn == execinfrapb.WindowerSpec_CUME_DIST {
    61  			constValue = 1
    62  		}
    63  		return NewConstOp(unlimitedAllocator, input, types.Float, constValue, outputColIdx)
    64  	}
    65  	rrInitFields := relativeRankInitFields{
    66  		rankInitFields: rankInitFields{
    67  			OneInputNode:    NewOneInputNode(input),
    68  			allocator:       unlimitedAllocator,
    69  			outputColIdx:    outputColIdx,
    70  			partitionColIdx: partitionColIdx,
    71  			peersColIdx:     peersColIdx,
    72  		},
    73  		memoryLimit:  memoryLimit,
    74  		diskQueueCfg: diskQueueCfg,
    75  		fdSemaphore:  fdSemaphore,
    76  		inputTypes:   inputTypes,
    77  		diskAcc:      diskAcc,
    78  	}
    79  	switch windowFn {
    80  	case execinfrapb.WindowerSpec_PERCENT_RANK:
    81  		if partitionColIdx != columnOmitted {
    82  			return &percentRankWithPartitionOp{
    83  				relativeRankInitFields: rrInitFields,
    84  			}, nil
    85  		}
    86  		return &percentRankNoPartitionOp{
    87  			relativeRankInitFields: rrInitFields,
    88  		}, nil
    89  	case execinfrapb.WindowerSpec_CUME_DIST:
    90  		if partitionColIdx != columnOmitted {
    91  			return &cumeDistWithPartitionOp{
    92  				relativeRankInitFields: rrInitFields,
    93  			}, nil
    94  		}
    95  		return &cumeDistNoPartitionOp{
    96  			relativeRankInitFields: rrInitFields,
    97  		}, nil
    98  	default:
    99  		return nil, errors.Errorf("unsupported relative rank type %s", windowFn)
   100  	}
   101  }
   102  
   103  // NOTE: in the context of window functions "partitions" mean a different thing
   104  // from "partition" in the context of external algorithms and some disk
   105  // infrastructure: here, "partitions" are sets of tuples that are not distinct
   106  // on the columns specified in PARTITION BY clause of the window function. If
   107  // such clause is omitted, then all tuples from the input belong to the same
   108  // partition.
   109  
   110  type relativeRankState int
   111  
   112  const (
   113  	// relativeRankBuffering is the state in which relativeRank operators fully
   114  	// buffer their input using spillingQueue. Additionally, the operators will
   115  	// be computing the sizes of the partitions and peer groups (if needed)
   116  	// using separate spillingQueues for each. Once a zero-length batch is
   117  	// received, the operator transitions to relativeRankEmitting state.
   118  	relativeRankBuffering relativeRankState = iota
   119  	// relativeRankEmitting is the state in which relativeRank operators emit
   120  	// the output. The output batch is populated by copying the next batch from
   121  	// the "buffered tuples" spilling queue and manually computing the output
   122  	// column for the window function using the already computed sizes of
   123  	// partitions and peer groups. Once a zero-length batch is dequeued from
   124  	// the "buffered tuples" queue, the operator transitions to
   125  	// relativeRankFinished state.
   126  	relativeRankEmitting
   127  	// relativeRankFinished is the state in which relativeRank operators close
   128  	// any non-closed disk resources and emit the zero-length batch.
   129  	relativeRankFinished
   130  )
   131  
   132  // {{/*
   133  // _COMPUTE_PARTITIONS_SIZES is a code snippet that computes the sizes of
   134  // partitions. It looks at i'th partitionCol value to check whether a new
   135  // partition begins at index i, and if so, it records the already computed
   136  // size of the previous partition into partitionsState.runningSizes vector.
   137  func _COMPUTE_PARTITIONS_SIZES() { // */}}
   138  	// {{define "computePartitionsSizes" -}}
   139  	if partitionCol[i] {
   140  		// We have encountered a start of a new partition, so we
   141  		// need to save the computed size of the previous one
   142  		// (if there was one).
   143  		if r.partitionsState.runningSizes == nil {
   144  			// TODO(yuzefovich): do not instantiate a new batch here once
   145  			// spillingQueues actually copy the batches when those are kept
   146  			// in-memory.
   147  			r.partitionsState.runningSizes = r.allocator.NewMemBatch([]*types.T{types.Int})
   148  			runningPartitionsSizesCol = r.partitionsState.runningSizes.ColVec(0).Int64()
   149  		}
   150  		if r.numTuplesInPartition > 0 {
   151  			runningPartitionsSizesCol[r.partitionsState.idx] = r.numTuplesInPartition
   152  			r.numTuplesInPartition = 0
   153  			r.partitionsState.idx++
   154  			if r.partitionsState.idx == coldata.BatchSize() {
   155  				// We need to flush the vector of partitions sizes.
   156  				r.partitionsState.runningSizes.SetLength(coldata.BatchSize())
   157  				if err := r.partitionsState.enqueue(ctx, r.partitionsState.runningSizes); err != nil {
   158  					colexecerror.InternalError(err)
   159  				}
   160  				r.partitionsState.runningSizes = nil
   161  				r.partitionsState.idx = 0
   162  			}
   163  		}
   164  	}
   165  	r.numTuplesInPartition++
   166  	// {{end}}
   167  	// {{/*
   168  } // */}}
   169  
   170  // {{/*
   171  // _COMPUTE_PEER_GROUPS_SIZES is a code snippet that computes the sizes of
   172  // peer groups. It looks at i'th peersCol value to check whether a new
   173  // peer group begins at index i, and if so, it records the already computed
   174  // size of the previous peer group into peerGroupsState.runningSizes vector.
   175  func _COMPUTE_PEER_GROUPS_SIZES() { // */}}
   176  	// {{define "computePeerGroupsSizes" -}}
   177  	if peersCol[i] {
   178  		// We have encountered a start of a new peer group, so we
   179  		// need to save the computed size of the previous one
   180  		// (if there was one).
   181  		if r.peerGroupsState.runningSizes == nil {
   182  			// TODO(yuzefovich): do not instantiate a new batch here once
   183  			// spillingQueues actually copy the batches when those are kept
   184  			// in-memory.
   185  			r.peerGroupsState.runningSizes = r.allocator.NewMemBatch([]*types.T{types.Int})
   186  			runningPeerGroupsSizesCol = r.peerGroupsState.runningSizes.ColVec(0).Int64()
   187  		}
   188  		if r.numPeers > 0 {
   189  			runningPeerGroupsSizesCol[r.peerGroupsState.idx] = r.numPeers
   190  			r.numPeers = 0
   191  			r.peerGroupsState.idx++
   192  			if r.peerGroupsState.idx == coldata.BatchSize() {
   193  				// We need to flush the vector of peer group sizes.
   194  				r.peerGroupsState.runningSizes.SetLength(coldata.BatchSize())
   195  				if err := r.peerGroupsState.enqueue(ctx, r.peerGroupsState.runningSizes); err != nil {
   196  					colexecerror.InternalError(err)
   197  				}
   198  				r.peerGroupsState.runningSizes = nil
   199  				r.peerGroupsState.idx = 0
   200  			}
   201  		}
   202  	}
   203  	r.numPeers++
   204  	// {{end}}
   205  	// {{/*
   206  } // */}}
   207  
   208  type relativeRankInitFields struct {
   209  	rankInitFields
   210  	closerHelper
   211  
   212  	state        relativeRankState
   213  	memoryLimit  int64
   214  	diskQueueCfg colcontainer.DiskQueueCfg
   215  	fdSemaphore  semaphore.Semaphore
   216  	inputTypes   []*types.T
   217  
   218  	diskAcc *mon.BoundAccount
   219  }
   220  
   221  type relativeRankSizesState struct {
   222  	*spillingQueue
   223  
   224  	// runningSizes is a batch consisting of a single int64 vector that stores
   225  	// sizes while we're computing them. Once all coldata.BatchSize() slots are
   226  	// filled, it will be flushed to the spillingQueue.
   227  	runningSizes coldata.Batch
   228  	// dequeuedSizes is a batch of already computed sizes that is dequeued
   229  	// from the spillingQueue.
   230  	dequeuedSizes coldata.Batch
   231  	// idx stores the index of the current slot in one of the batches above
   232  	// that we're currently working with.
   233  	idx int
   234  }
   235  
   236  // relativeRankUtilityQueueMemLimitFraction defines the fraction of the memory
   237  // limit that will be given to the "utility" spillingQueues of relativeRank
   238  // operators (i.e. non "buffered tuples" queues).
   239  const relativeRankUtilityQueueMemLimitFraction = 0.1
   240  
   241  // {{range .}}
   242  
   243  type _RELATIVE_RANK_STRINGOp struct {
   244  	relativeRankInitFields
   245  
   246  	// mu is used to protect against concurrent IdempotentClose and Next calls,
   247  	// which are currently allowed.
   248  	// TODO(asubiotto): Explore calling IdempotentClose from the same goroutine as
   249  	//  Next, which will simplify this model.
   250  	mu syncutil.Mutex
   251  
   252  	// {{if .IsPercentRank}}
   253  	// rank indicates which rank should be assigned to the next tuple.
   254  	rank int64
   255  	// rankIncrement indicates by how much rank should be incremented when a
   256  	// tuple distinct from the previous one on the ordering columns is seen.
   257  	rankIncrement int64
   258  	// {{end}}
   259  
   260  	// {{if .IsCumeDist}}
   261  	peerGroupsState relativeRankSizesState
   262  	// numPrecedingTuples stores the number of tuples preceding to the first
   263  	// peer of the current tuple in the current partition.
   264  	numPrecedingTuples int64
   265  	// numPeers stores the number of tuples that are peers with the current
   266  	// tuple.
   267  	numPeers int64
   268  	// {{end}}
   269  
   270  	// {{if .HasPartition}}
   271  	partitionsState relativeRankSizesState
   272  	// {{end}}
   273  	// numTuplesInPartition contains the number of tuples in the current
   274  	// partition.
   275  	numTuplesInPartition int64
   276  
   277  	bufferedTuples *spillingQueue
   278  	scratch        coldata.Batch
   279  	output         coldata.Batch
   280  }
   281  
   282  var _ closableOperator = &_RELATIVE_RANK_STRINGOp{}
   283  
   284  func (r *_RELATIVE_RANK_STRINGOp) Init() {
   285  	r.Input().Init()
   286  	r.state = relativeRankBuffering
   287  	usedMemoryLimitFraction := 0.0
   288  	// {{if .HasPartition}}
   289  	r.partitionsState.spillingQueue = newSpillingQueue(
   290  		r.allocator, []*types.T{types.Int},
   291  		int64(float64(r.memoryLimit)*relativeRankUtilityQueueMemLimitFraction),
   292  		r.diskQueueCfg, r.fdSemaphore, coldata.BatchSize(), r.diskAcc,
   293  	)
   294  	usedMemoryLimitFraction += relativeRankUtilityQueueMemLimitFraction
   295  	// {{end}}
   296  	// {{if .IsCumeDist}}
   297  	r.peerGroupsState.spillingQueue = newSpillingQueue(
   298  		r.allocator, []*types.T{types.Int},
   299  		int64(float64(r.memoryLimit)*relativeRankUtilityQueueMemLimitFraction),
   300  		r.diskQueueCfg, r.fdSemaphore, coldata.BatchSize(), r.diskAcc,
   301  	)
   302  	usedMemoryLimitFraction += relativeRankUtilityQueueMemLimitFraction
   303  	// {{end}}
   304  	r.bufferedTuples = newSpillingQueue(
   305  		r.allocator, r.inputTypes,
   306  		int64(float64(r.memoryLimit)*(1.0-usedMemoryLimitFraction)),
   307  		r.diskQueueCfg, r.fdSemaphore, coldata.BatchSize(), r.diskAcc,
   308  	)
   309  	r.output = r.allocator.NewMemBatch(append(r.inputTypes, types.Float))
   310  	// {{if .IsPercentRank}}
   311  	// All rank functions start counting from 1. Before we assign the rank to a
   312  	// tuple in the batch, we first increment r.rank, so setting this
   313  	// rankIncrement to 1 will update r.rank to 1 on the very first tuple (as
   314  	// desired).
   315  	r.rankIncrement = 1
   316  	// {{end}}
   317  }
   318  
   319  func (r *_RELATIVE_RANK_STRINGOp) Next(ctx context.Context) coldata.Batch {
   320  	r.mu.Lock()
   321  	defer r.mu.Unlock()
   322  	var err error
   323  	for {
   324  		switch r.state {
   325  		case relativeRankBuffering:
   326  			// The outline of what we need to do in "buffering" state:
   327  			//
   328  			// 1. we need to buffer the tuples that we read from the input.
   329  			// These are simply copied into r.bufferedTuples spillingQueue.
   330  			//
   331  			// 2. (if we have PARTITION BY clause) we need to compute the sizes of
   332  			// partitions. These sizes are stored in r.partitionsState.runningSizes
   333  			// batch (that consists of a single vector) and r.partitionsState.idx
   334  			// points at the next slot in that vector to write to. Once it
   335  			// reaches coldata.BatchSize(), the batch is "flushed" to the
   336  			// corresponding spillingQueue. The "running" value of the current
   337  			// partition size is stored in r.numTuplesInPartition.
   338  			//
   339  			// 3. (if we have CUME_DIST function) we need to compute the sizes
   340  			// of peer groups. These sizes are stored in r.peerGroupsState.runningSizes
   341  			// batch (that consists of a single vector) and r.peerGroupsState.idx
   342  			// points at the next slot in that vector to write to. Once it
   343  			// reaches coldata.BatchSize(), the batch is "flushed" to the
   344  			// corresponding spillingQueue. The "running" value of the current
   345  			// peer group size is stored in r.numPeers.
   346  			//
   347  			// For example, if we have the following setup:
   348  			//   partitionCol = {true, false, false, true, false, false, false, false}
   349  			//   peersCol     = {true, false, true, true, false, false, true, false}
   350  			// we want this as the result:
   351  			//   partitionsSizes = {3, 5}
   352  			//   peerGroupsSizes = {2, 1, 3, 2}.
   353  			// This example also shows why we need to use two different queues
   354  			// (since every partition can have multiple peer groups, the
   355  			// schedule of "flushing" is different).
   356  			batch := r.Input().Next(ctx)
   357  			n := batch.Length()
   358  			if n == 0 {
   359  				if err := r.bufferedTuples.enqueue(ctx, coldata.ZeroBatch); err != nil {
   360  					colexecerror.InternalError(err)
   361  				}
   362  				// {{if .HasPartition}}
   363  				// We need to flush the last vector of the running partitions
   364  				// sizes, including the very last partition.
   365  				if r.partitionsState.runningSizes == nil {
   366  					// TODO(yuzefovich): do not instantiate a new batch here once
   367  					// spillingQueues actually copy the batches when those are kept
   368  					// in-memory.
   369  					r.partitionsState.runningSizes = r.allocator.NewMemBatch([]*types.T{types.Int})
   370  				}
   371  				runningPartitionsSizesCol := r.partitionsState.runningSizes.ColVec(0).Int64()
   372  				runningPartitionsSizesCol[r.partitionsState.idx] = r.numTuplesInPartition
   373  				r.partitionsState.idx++
   374  				r.partitionsState.runningSizes.SetLength(r.partitionsState.idx)
   375  				if err := r.partitionsState.enqueue(ctx, r.partitionsState.runningSizes); err != nil {
   376  					colexecerror.InternalError(err)
   377  				}
   378  				if err := r.partitionsState.enqueue(ctx, coldata.ZeroBatch); err != nil {
   379  					colexecerror.InternalError(err)
   380  				}
   381  				// {{end}}
   382  				// {{if .IsCumeDist}}
   383  				// We need to flush the last vector of the running peer groups
   384  				// sizes, including the very last peer group.
   385  				if r.peerGroupsState.runningSizes == nil {
   386  					// TODO(yuzefovich): do not instantiate a new batch here once
   387  					// spillingQueues actually copy the batches when those are kept
   388  					// in-memory.
   389  					r.peerGroupsState.runningSizes = r.allocator.NewMemBatch([]*types.T{types.Int})
   390  				}
   391  				runningPeerGroupsSizesCol := r.peerGroupsState.runningSizes.ColVec(0).Int64()
   392  				runningPeerGroupsSizesCol[r.peerGroupsState.idx] = r.numPeers
   393  				r.peerGroupsState.idx++
   394  				r.peerGroupsState.runningSizes.SetLength(r.peerGroupsState.idx)
   395  				if err := r.peerGroupsState.enqueue(ctx, r.peerGroupsState.runningSizes); err != nil {
   396  					colexecerror.InternalError(err)
   397  				}
   398  				if err := r.peerGroupsState.enqueue(ctx, coldata.ZeroBatch); err != nil {
   399  					colexecerror.InternalError(err)
   400  				}
   401  				// {{end}}
   402  				// We have fully consumed the input, so now we can populate the output.
   403  				r.state = relativeRankEmitting
   404  				continue
   405  			}
   406  
   407  			// {{if .HasPartition}}
   408  			// For simplicity, we will fully consume the input before we start
   409  			// producing the output.
   410  			// TODO(yuzefovich): we could be emitting output once we see that a new
   411  			// partition has begun.
   412  			// {{else}}
   413  			// All tuples belong to the same partition, so we need to fully consume
   414  			// the input before we can proceed.
   415  			// {{end}}
   416  
   417  			sel := batch.Selection()
   418  			// First, we buffer up all of the tuples.
   419  			// TODO(yuzefovich): do not instantiate a new batch here once
   420  			// spillingQueues actually copy the batches when those are kept
   421  			// in-memory.
   422  			r.scratch = r.allocator.NewMemBatchWithSize(r.inputTypes, n)
   423  			r.allocator.PerformOperation(r.scratch.ColVecs(), func() {
   424  				for colIdx, vec := range r.scratch.ColVecs() {
   425  					vec.Copy(
   426  						coldata.CopySliceArgs{
   427  							SliceArgs: coldata.SliceArgs{
   428  								Src:       batch.ColVec(colIdx),
   429  								Sel:       sel,
   430  								SrcEndIdx: n,
   431  							},
   432  						},
   433  					)
   434  				}
   435  				r.scratch.SetLength(n)
   436  			})
   437  			if err := r.bufferedTuples.enqueue(ctx, r.scratch); err != nil {
   438  				colexecerror.InternalError(err)
   439  			}
   440  
   441  			// Then, we need to update the sizes of the partitions.
   442  			// {{if .HasPartition}}
   443  			partitionCol := batch.ColVec(r.partitionColIdx).Bool()
   444  			var runningPartitionsSizesCol []int64
   445  			if r.partitionsState.runningSizes != nil {
   446  				runningPartitionsSizesCol = r.partitionsState.runningSizes.ColVec(0).Int64()
   447  			}
   448  			if sel != nil {
   449  				for _, i := range sel[:n] {
   450  					_COMPUTE_PARTITIONS_SIZES()
   451  				}
   452  			} else {
   453  				for i := 0; i < n; i++ {
   454  					_COMPUTE_PARTITIONS_SIZES()
   455  				}
   456  			}
   457  			// {{else}}
   458  			// There is a single partition in the whole input.
   459  			r.numTuplesInPartition += int64(n)
   460  			// {{end}}
   461  
   462  			// {{if .IsCumeDist}}
   463  			// Next, we need to update the sizes of the peer groups.
   464  			peersCol := batch.ColVec(r.peersColIdx).Bool()
   465  			var runningPeerGroupsSizesCol []int64
   466  			if r.peerGroupsState.runningSizes != nil {
   467  				runningPeerGroupsSizesCol = r.peerGroupsState.runningSizes.ColVec(0).Int64()
   468  			}
   469  			if sel != nil {
   470  				for _, i := range sel[:n] {
   471  					_COMPUTE_PEER_GROUPS_SIZES()
   472  				}
   473  			} else {
   474  				for i := 0; i < n; i++ {
   475  					_COMPUTE_PEER_GROUPS_SIZES()
   476  				}
   477  			}
   478  			// {{end}}
   479  			continue
   480  
   481  		case relativeRankEmitting:
   482  			if r.scratch, err = r.bufferedTuples.dequeue(ctx); err != nil {
   483  				colexecerror.InternalError(err)
   484  			}
   485  			n := r.scratch.Length()
   486  			if n == 0 {
   487  				r.state = relativeRankFinished
   488  				continue
   489  			}
   490  			// {{if .HasPartition}}
   491  			// Get the next batch of partition sizes if we haven't already.
   492  			if r.partitionsState.dequeuedSizes == nil {
   493  				if r.partitionsState.dequeuedSizes, err = r.partitionsState.dequeue(ctx); err != nil {
   494  					colexecerror.InternalError(err)
   495  				}
   496  				r.partitionsState.idx = 0
   497  				r.numTuplesInPartition = 0
   498  			}
   499  			// {{end}}
   500  			// {{if .IsCumeDist}}
   501  			// Get the next batch of peer group sizes if we haven't already.
   502  			if r.peerGroupsState.dequeuedSizes == nil {
   503  				if r.peerGroupsState.dequeuedSizes, err = r.peerGroupsState.dequeue(ctx); err != nil {
   504  					colexecerror.InternalError(err)
   505  				}
   506  				r.peerGroupsState.idx = 0
   507  				r.numPeers = 0
   508  			}
   509  			// {{end}}
   510  
   511  			r.output.ResetInternalBatch()
   512  			// First, we copy over the buffered up columns.
   513  			r.allocator.PerformOperation(r.output.ColVecs()[:len(r.inputTypes)], func() {
   514  				for colIdx, vec := range r.output.ColVecs()[:len(r.inputTypes)] {
   515  					vec.Copy(
   516  						coldata.CopySliceArgs{
   517  							SliceArgs: coldata.SliceArgs{
   518  								Src:       r.scratch.ColVec(colIdx),
   519  								SrcEndIdx: n,
   520  							},
   521  						},
   522  					)
   523  				}
   524  			})
   525  
   526  			// Now we will populate the output column.
   527  			relativeRankOutputCol := r.output.ColVec(r.outputColIdx).Float64()
   528  			// {{if .HasPartition}}
   529  			partitionCol := r.scratch.ColVec(r.partitionColIdx).Bool()
   530  			// {{end}}
   531  			peersCol := r.scratch.ColVec(r.peersColIdx).Bool()
   532  			// We don't need to think about the selection vector since all the
   533  			// buffered up tuples have been "deselected" during the buffering
   534  			// stage.
   535  			for i := range relativeRankOutputCol[:n] {
   536  				// We need to set r.numTuplesInPartition to the size of the
   537  				// partition that i'th tuple belongs to (which we have already
   538  				// computed).
   539  				// {{if .HasPartition}}
   540  				if partitionCol[i] {
   541  					if r.partitionsState.idx == r.partitionsState.dequeuedSizes.Length() {
   542  						if r.partitionsState.dequeuedSizes, err = r.partitionsState.dequeue(ctx); err != nil {
   543  							colexecerror.InternalError(err)
   544  						}
   545  						r.partitionsState.idx = 0
   546  					}
   547  					r.numTuplesInPartition = r.partitionsState.dequeuedSizes.ColVec(0).Int64()[r.partitionsState.idx]
   548  					r.partitionsState.idx++
   549  					// {{if .IsPercentRank}}
   550  					// We need to reset the internal state because of the new
   551  					// partition.
   552  					r.rank = 0
   553  					r.rankIncrement = 1
   554  					// {{end}}
   555  					// {{if .IsCumeDist}}
   556  					// We need to reset the number of preceding tuples because of the
   557  					// new partition.
   558  					r.numPrecedingTuples = 0
   559  					r.numPeers = 0
   560  					// {{end}}
   561  				}
   562  				// {{else}}
   563  				// There is a single partition in the whole input, and
   564  				// r.numTuplesInPartition already contains the correct number.
   565  				// {{end}}
   566  
   567  				if peersCol[i] {
   568  					// {{if .IsPercentRank}}
   569  					r.rank += r.rankIncrement
   570  					r.rankIncrement = 0
   571  					// {{end}}
   572  					// {{if .IsCumeDist}}
   573  					// We have encountered a new peer group, and we need to update the
   574  					// number of preceding tuples and get the number of tuples in
   575  					// this peer group.
   576  					r.numPrecedingTuples += r.numPeers
   577  					if r.peerGroupsState.idx == r.peerGroupsState.dequeuedSizes.Length() {
   578  						if r.peerGroupsState.dequeuedSizes, err = r.peerGroupsState.dequeue(ctx); err != nil {
   579  							colexecerror.InternalError(err)
   580  						}
   581  						r.peerGroupsState.idx = 0
   582  					}
   583  					r.numPeers = r.peerGroupsState.dequeuedSizes.ColVec(0).Int64()[r.peerGroupsState.idx]
   584  					r.peerGroupsState.idx++
   585  					// {{end}}
   586  				}
   587  
   588  				// Now we can compute the value of the window function for i'th
   589  				// tuple.
   590  				// {{if .IsPercentRank}}
   591  				if r.numTuplesInPartition == 1 {
   592  					// There is a single tuple in the partition, so we return 0, per spec.
   593  					relativeRankOutputCol[i] = 0
   594  				} else {
   595  					relativeRankOutputCol[i] = float64(r.rank-1) / float64(r.numTuplesInPartition-1)
   596  				}
   597  				r.rankIncrement++
   598  				// {{end}}
   599  				// {{if .IsCumeDist}}
   600  				relativeRankOutputCol[i] = float64(r.numPrecedingTuples+r.numPeers) / float64(r.numTuplesInPartition)
   601  				// {{end}}
   602  			}
   603  			r.output.SetLength(n)
   604  			return r.output
   605  
   606  		case relativeRankFinished:
   607  			if err := r.idempotentCloseLocked(ctx); err != nil {
   608  				colexecerror.InternalError(err)
   609  			}
   610  			return coldata.ZeroBatch
   611  
   612  		default:
   613  			colexecerror.InternalError("percent rank operator in unhandled state")
   614  			// This code is unreachable, but the compiler cannot infer that.
   615  			return nil
   616  		}
   617  	}
   618  }
   619  
   620  func (r *_RELATIVE_RANK_STRINGOp) IdempotentClose(ctx context.Context) error {
   621  	r.mu.Lock()
   622  	defer r.mu.Unlock()
   623  	return r.idempotentCloseLocked(ctx)
   624  }
   625  
   626  func (r *_RELATIVE_RANK_STRINGOp) idempotentCloseLocked(ctx context.Context) error {
   627  	if !r.close() {
   628  		return nil
   629  	}
   630  	var lastErr error
   631  	if err := r.bufferedTuples.close(ctx); err != nil {
   632  		lastErr = err
   633  	}
   634  	// {{if .HasPartition}}
   635  	if err := r.partitionsState.close(ctx); err != nil {
   636  		lastErr = err
   637  	}
   638  	// {{end}}
   639  	// {{if .IsCumeDist}}
   640  	if err := r.peerGroupsState.close(ctx); err != nil {
   641  		lastErr = err
   642  	}
   643  	// {{end}}
   644  	return lastErr
   645  }
   646  
   647  // {{end}}