You are an expert in writing code in many different languages. Your goal is to perform code completion for the following code, keeping in mind the rest of the code and the file meta data.
    Metadata details:
    filename: batch.go
    path of file: ./repos/cockroach/pkg/kv/kvclient/kvcoord
    
    the code of the file until where you have to start completion: // Copyright 2015 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package kvcoord

import (
	"sort"
	"unsafe"

	"github.com/cockroachdb/cockroach/pkg/keys"
	"github.com/cockroachdb/cockroach/pkg/kv/kvpb"
	"github.com/cockroachdb/cockroach/pkg/roachpb"
	"github.com/cockroachdb/cockroach/pkg/util/buildutil"
	"github.com/cockroachdb/errors"
)

// BatchTruncationHelper is a utility struct that helps with truncating requests
// to range boundaries as well as figuring out the next key to seek to for the
// range iterator.
//
// The caller should not use the helper if all requests fit within a single
// range since the helper has non-trivial setup cost.
//
// It is designed to be used roughly as follows:
//
//	rs := keys.Range(requests)
//	ri.Seek(scanDir, rs.Key)
//	if !ri.NeedAnother(rs) {
//	  // All requests fit within a single range, don't use the helper.
//	  ...
//	}
//	helper := NewBatchTruncationHelper(scanDir, requests)
//	for ri.Valid() {
//	  curRangeRS := rs.Intersect(ri.Token().Desc())

    
func (h *BatchTruncationHelper) truncateAsc(rs roachpb.RSpan) ([]kvpb.RequestUnion, []int, error) {
	var truncReqs []kvpb.RequestUnion
	var positions []int
	for i := h.startIdx; i < len(h.positions); i++ {
		pos := h.positions[i]
		if pos < 0 {
			// This request has already been fully processed, so there is no
			// need to look at it.
			continue
		}
		header := h.headers[i]
		// rs.EndKey can't be local because it contains range split points,
		// which are never local.
		ek := rs.EndKey.AsRawKey()
		if ek.Compare(header.Key) <= 0 {
			// All of the remaining requests start after this range, so we're
			// done.
			break
		}
		if !h.isRange[i] {
			// This is a point request, and the key is contained within this
			// range, so we include the request as is and mark it as "fully
			// processed".
			truncReqs = append(truncReqs, h.requests[i])
			positions = append(positions, pos)
			h.headers[i] = kvpb.RequestHeader{}
			h.positions[i] = -1
			continue
		}
		// We're dealing with a range-spanning request.
		if buildutil.CrdbTestBuild {
			// rs.Key can't be local because it contains range split points,
			// which are never local.
			if header.Key.Compare(rs.Key.AsRawKey()) < 0 {
				return nil, nil, errors.AssertionFailedf(
					"unexpectedly header.Key %s is less than rs %s", header.Key, rs,
				)
			}
		}
		inner := h.requests[i].GetInner()
		if header.EndKey.Compare(ek) <= 0 {
			// This is the last part of this request since it is fully contained
			// within this range, so we mark the request as "fully processed".
			h.headers[i] = kvpb.RequestHeader{}
			h.positions[i] = -1
			if origStartKey := inner.Header().Key; origStartKey.Equal(header.Key) {
				// This range-spanning request fits within a single range, so we
				// can just use the original request.
				truncReqs = append(truncReqs, h.requests[i])
				positions = append(positions, pos)
				continue
			}
		} else {
			header.EndKey = ek
			// Adjust the start key of the header so that it contained only the
			// unprocessed suffix of the request.
			h.headers[i].Key = header.EndKey
		}
		shallowCopy := inner.ShallowCopy()
		shallowCopy.SetHeader(header)
		truncReqs = append(truncReqs, kvpb.RequestUnion{})
		truncReqs[len(truncReqs)-1].MustSetInner(shallowCopy)
		positions = append(positions, pos)
	}
	return truncReqs, positions, nil
}