github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/index/index_lookup.go (about)

     1  // Copyright 2020 Dolthub, 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  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package index
    16  
    17  import (
    18  	"context"
    19  	"encoding/binary"
    20  	"fmt"
    21  	"io"
    22  
    23  	"github.com/dolthub/go-mysql-server/sql"
    24  
    25  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/row"
    28  	"github.com/dolthub/dolt/go/libraries/doltcore/table/typed/noms"
    29  	"github.com/dolthub/dolt/go/store/prolly"
    30  	"github.com/dolthub/dolt/go/store/prolly/tree"
    31  	"github.com/dolthub/dolt/go/store/types"
    32  	"github.com/dolthub/dolt/go/store/val"
    33  )
    34  
    35  func RowIterForIndexLookup(ctx *sql.Context, t DoltTableable, lookup sql.IndexLookup, pkSch sql.PrimaryKeySchema, columns []uint64) (sql.RowIter, error) {
    36  	idx := lookup.Index.(*doltIndex)
    37  	durableState, err := idx.getDurableState(ctx, t)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	if types.IsFormat_DOLT(idx.Format()) {
    43  		prollyRanges, err := idx.prollyRanges(ctx, idx.ns, lookup.Ranges...)
    44  		if len(prollyRanges) > 1 {
    45  			return nil, fmt.Errorf("expected a single index range")
    46  		}
    47  		if err != nil {
    48  			return nil, err
    49  		}
    50  		return RowIterForProllyRange(ctx, idx, prollyRanges[0], pkSch, columns, durableState)
    51  	} else {
    52  		nomsRanges, err := idx.nomsRanges(ctx, lookup.Ranges...)
    53  		if err != nil {
    54  			return nil, err
    55  		}
    56  		return RowIterForNomsRanges(ctx, idx, nomsRanges, columns, durableState)
    57  	}
    58  }
    59  
    60  func RowIterForProllyRange(ctx *sql.Context, idx DoltIndex, r prolly.Range, pkSch sql.PrimaryKeySchema, projections []uint64, durableState *durableIndexState) (sql.RowIter, error) {
    61  	if projections == nil {
    62  		projections = idx.Schema().GetAllCols().Tags
    63  	}
    64  
    65  	if sql.IsKeyless(pkSch.Schema) {
    66  		// in order to resolve row cardinality, keyless indexes must always perform
    67  		// an indirect lookup through the clustered index.
    68  		return newProllyKeylessIndexIter(ctx, idx, r, pkSch, projections, durableState.Primary, durableState.Secondary)
    69  	}
    70  
    71  	covers := idx.coversColumns(durableState, projections)
    72  	if covers {
    73  		return newProllyCoveringIndexIter(ctx, idx, r, pkSch, projections, durableState.Secondary)
    74  	}
    75  	return newProllyIndexIter(ctx, idx, r, pkSch, projections, durableState.Primary, durableState.Secondary)
    76  }
    77  
    78  func RowIterForNomsRanges(ctx *sql.Context, idx DoltIndex, ranges []*noms.ReadRange, columns []uint64, durableState *durableIndexState) (sql.RowIter, error) {
    79  	if len(columns) == 0 {
    80  		columns = idx.Schema().GetAllCols().Tags
    81  	}
    82  	m := durable.NomsMapFromIndex(durableState.Secondary)
    83  	nrr := noms.NewNomsRangeReader(idx.valueReadWriter(), idx.IndexSchema(), m, ranges)
    84  
    85  	covers := idx.coversColumns(durableState, columns)
    86  	if covers || idx.ID() == "PRIMARY" {
    87  		return NewCoveringIndexRowIterAdapter(ctx, idx, nrr, columns), nil
    88  	} else {
    89  		return NewIndexLookupRowIterAdapter(ctx, idx, durableState, nrr, columns)
    90  	}
    91  }
    92  
    93  type IndexLookupKeyIterator interface {
    94  	// NextKey returns the next key if it exists, and io.EOF if it does not.
    95  	NextKey(ctx *sql.Context) (row.TaggedValues, error)
    96  }
    97  
    98  func NewRangePartitionIter(ctx *sql.Context, t DoltTableable, lookup sql.IndexLookup, isDoltFmt bool) (sql.PartitionIter, error) {
    99  	idx := lookup.Index.(*doltIndex)
   100  	if lookup.IsPointLookup && isDoltFmt {
   101  		return newPointPartitionIter(ctx, lookup, idx)
   102  	}
   103  
   104  	var prollyRanges []prolly.Range
   105  	var nomsRanges []*noms.ReadRange
   106  	var err error
   107  	if isDoltFmt {
   108  		prollyRanges, err = idx.prollyRanges(ctx, idx.ns, lookup.Ranges...)
   109  	} else {
   110  		nomsRanges, err = idx.nomsRanges(ctx, lookup.Ranges...)
   111  	}
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	return &rangePartitionIter{
   116  		nomsRanges:   nomsRanges,
   117  		prollyRanges: prollyRanges,
   118  		curr:         0,
   119  		isDoltFmt:    isDoltFmt,
   120  		isReverse:    lookup.IsReverse,
   121  	}, nil
   122  }
   123  
   124  func newPointPartitionIter(ctx *sql.Context, lookup sql.IndexLookup, idx *doltIndex) (sql.PartitionIter, error) {
   125  	prollyRanges, err := idx.prollyRanges(ctx, idx.ns, lookup.Ranges[0])
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	return &pointPartition{
   130  		r: prollyRanges[0],
   131  	}, nil
   132  }
   133  
   134  var _ sql.PartitionIter = (*pointPartition)(nil)
   135  var _ sql.Partition = (*pointPartition)(nil)
   136  
   137  type pointPartition struct {
   138  	r    prolly.Range
   139  	used bool
   140  }
   141  
   142  func (p pointPartition) Key() []byte {
   143  	return []byte{0}
   144  }
   145  
   146  func (p *pointPartition) Close(c *sql.Context) error {
   147  	return nil
   148  }
   149  
   150  func (p *pointPartition) Next(c *sql.Context) (sql.Partition, error) {
   151  	if p.used {
   152  		return nil, io.EOF
   153  	}
   154  	p.used = true
   155  	return *p, nil
   156  }
   157  
   158  type rangePartitionIter struct {
   159  	nomsRanges   []*noms.ReadRange
   160  	prollyRanges []prolly.Range
   161  	curr         int
   162  	isDoltFmt    bool
   163  	isReverse    bool
   164  }
   165  
   166  // Close is required by the sql.PartitionIter interface. Does nothing.
   167  func (itr *rangePartitionIter) Close(*sql.Context) error {
   168  	return nil
   169  }
   170  
   171  // Next returns the next partition if there is one, or io.EOF if there isn't.
   172  func (itr *rangePartitionIter) Next(_ *sql.Context) (sql.Partition, error) {
   173  	if itr.isDoltFmt {
   174  		return itr.nextProllyPartition()
   175  	}
   176  	return itr.nextNomsPartition()
   177  }
   178  
   179  func (itr *rangePartitionIter) nextProllyPartition() (sql.Partition, error) {
   180  	if itr.curr >= len(itr.prollyRanges) {
   181  		return nil, io.EOF
   182  	}
   183  
   184  	var bytes [4]byte
   185  	binary.BigEndian.PutUint32(bytes[:], uint32(itr.curr))
   186  	pr := itr.prollyRanges[itr.curr]
   187  	itr.curr += 1
   188  
   189  	return rangePartition{
   190  		prollyRange: pr,
   191  		key:         bytes[:],
   192  		isReverse:   itr.isReverse,
   193  	}, nil
   194  }
   195  
   196  func (itr *rangePartitionIter) nextNomsPartition() (sql.Partition, error) {
   197  	if itr.curr >= len(itr.nomsRanges) {
   198  		return nil, io.EOF
   199  	}
   200  
   201  	var bytes [4]byte
   202  	binary.BigEndian.PutUint32(bytes[:], uint32(itr.curr))
   203  	nr := itr.nomsRanges[itr.curr]
   204  	itr.curr += 1
   205  
   206  	return rangePartition{
   207  		nomsRange: nr,
   208  		key:       bytes[:],
   209  		isReverse: itr.isReverse,
   210  	}, nil
   211  }
   212  
   213  type rangePartition struct {
   214  	nomsRange   *noms.ReadRange
   215  	prollyRange prolly.Range
   216  	key         []byte
   217  	isReverse   bool
   218  }
   219  
   220  func (rp rangePartition) Key() []byte {
   221  	return rp.key
   222  }
   223  
   224  // LookupBuilder generates secondary lookups for partitions and
   225  // encapsulates fast path optimizations for certain point lookups.
   226  type LookupBuilder interface {
   227  	// NewRowIter returns a new index iter for the given partition
   228  	NewRowIter(ctx *sql.Context, part sql.Partition) (sql.RowIter, error)
   229  	Key() doltdb.DataCacheKey
   230  }
   231  
   232  func NewLookupBuilder(
   233  	ctx *sql.Context,
   234  	tab DoltTableable,
   235  	idx DoltIndex,
   236  	key doltdb.DataCacheKey,
   237  	projections []uint64,
   238  	pkSch sql.PrimaryKeySchema,
   239  	isDoltFormat bool,
   240  ) (LookupBuilder, error) {
   241  	if projections == nil {
   242  		projections = idx.Schema().GetAllCols().Tags
   243  	}
   244  
   245  	di := idx.(*doltIndex)
   246  	s, err := di.getDurableState(ctx, tab)
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  	base := &baseLookupBuilder{
   251  		idx:         di,
   252  		key:         key,
   253  		sch:         pkSch,
   254  		projections: projections,
   255  	}
   256  
   257  	if isDoltFormat {
   258  		base.sec = durable.ProllyMapFromIndex(s.Secondary)
   259  		base.secKd, base.secVd = base.sec.Descriptors()
   260  		base.ns = base.sec.NodeStore()
   261  		base.prefDesc = base.secKd.PrefixDesc(len(di.columns))
   262  	}
   263  
   264  	switch {
   265  	case !isDoltFormat:
   266  		return &nomsLookupBuilder{
   267  			baseLookupBuilder: base,
   268  			s:                 s,
   269  		}, nil
   270  	case sql.IsKeyless(pkSch.Schema):
   271  		return &keylessLookupBuilder{
   272  			baseLookupBuilder: base,
   273  			s:                 s,
   274  		}, nil
   275  	case idx.coversColumns(s, projections):
   276  		return newCoveringLookupBuilder(base), nil
   277  	case idx.ID() == "PRIMARY":
   278  		// If we are using the primary index, always use a covering lookup builder. In some cases, coversColumns
   279  		// can return false, for example if a column was modified in an older version and has a different tag than
   280  		// the current schema. In those cases, the primary index is still the best we have, so go ahead and use it.
   281  		return newCoveringLookupBuilder(base), nil
   282  	default:
   283  		return newNonCoveringLookupBuilder(s, base)
   284  	}
   285  }
   286  
   287  func newCoveringLookupBuilder(b *baseLookupBuilder) *coveringLookupBuilder {
   288  	var keyMap, valMap, ordMap val.OrdinalMapping
   289  	if b.idx.IsPrimaryKey() {
   290  		keyMap, valMap, ordMap = primaryIndexMapping(b.idx, b.sch, b.projections)
   291  	} else {
   292  		keyMap, ordMap = coveringIndexMapping(b.idx, b.projections)
   293  	}
   294  	return &coveringLookupBuilder{
   295  		baseLookupBuilder: b,
   296  		keyMap:            keyMap,
   297  		valMap:            valMap,
   298  		ordMap:            ordMap,
   299  	}
   300  }
   301  
   302  // newNonCoveringLookupBuilder returns a LookupBuilder that uses the specified index state and
   303  // base lookup builder to create a nonCoveringLookupBuilder that uses the secondary index (from
   304  // |b|) to find the PK row identifier, and then uses that PK to look up the complete row from
   305  // the primary index (from |s|). If a baseLookupBuilder built on the primary index is passed in,
   306  // this function returns an error.
   307  func newNonCoveringLookupBuilder(s *durableIndexState, b *baseLookupBuilder) (*nonCoveringLookupBuilder, error) {
   308  	if b.idx.ID() == "PRIMARY" {
   309  		return nil, fmt.Errorf("incompatible index passed to newNonCoveringLookupBuilder: " +
   310  			"primary index passed, but only secondary indexes are supported")
   311  	}
   312  
   313  	primary := durable.ProllyMapFromIndex(s.Primary)
   314  	priKd, _ := primary.Descriptors()
   315  	tbBld := val.NewTupleBuilder(priKd)
   316  	pkMap := ordinalMappingFromIndex(b.idx)
   317  	keyProj, valProj, ordProj := projectionMappings(b.idx.Schema(), b.projections)
   318  	return &nonCoveringLookupBuilder{
   319  		baseLookupBuilder: b,
   320  		pri:               primary,
   321  		priKd:             priKd,
   322  		pkBld:             tbBld,
   323  		pkMap:             pkMap,
   324  		keyMap:            keyProj,
   325  		valMap:            valProj,
   326  		ordMap:            ordProj,
   327  	}, nil
   328  }
   329  
   330  var _ LookupBuilder = (*baseLookupBuilder)(nil)
   331  var _ LookupBuilder = (*nomsLookupBuilder)(nil)
   332  var _ LookupBuilder = (*coveringLookupBuilder)(nil)
   333  var _ LookupBuilder = (*keylessLookupBuilder)(nil)
   334  var _ LookupBuilder = (*nonCoveringLookupBuilder)(nil)
   335  
   336  // baseLookupBuilder is a common lookup builder for prolly covering and
   337  // non covering index lookups.
   338  type baseLookupBuilder struct {
   339  	key doltdb.DataCacheKey
   340  
   341  	idx         *doltIndex
   342  	sch         sql.PrimaryKeySchema
   343  	projections []uint64
   344  
   345  	sec          prolly.Map
   346  	secKd, secVd val.TupleDesc
   347  	prefDesc     val.TupleDesc
   348  	ns           tree.NodeStore
   349  }
   350  
   351  func (lb *baseLookupBuilder) Key() doltdb.DataCacheKey {
   352  	return lb.key
   353  }
   354  
   355  // NewRowIter implements IndexLookup
   356  func (lb *baseLookupBuilder) NewRowIter(ctx *sql.Context, part sql.Partition) (sql.RowIter, error) {
   357  	panic("cannot call NewRowIter on baseLookupBuilder")
   358  }
   359  
   360  // newPointLookup will create a cursor once, and then use the same cursor for
   361  // every subsequent point lookup. Note that equality joins can have a mix of
   362  // point lookups on concrete values, and range lookups for null matches.
   363  func (lb *baseLookupBuilder) newPointLookup(ctx *sql.Context, rang prolly.Range) (iter prolly.MapIter, err error) {
   364  	err = lb.sec.GetPrefix(ctx, rang.Tup, lb.prefDesc, func(key val.Tuple, value val.Tuple) (err error) {
   365  		if key != nil && rang.Matches(key) {
   366  			iter = prolly.NewPointLookup(key, value)
   367  		} else {
   368  			iter = prolly.EmptyPointLookup
   369  		}
   370  		return
   371  	})
   372  	return
   373  }
   374  
   375  func (lb *baseLookupBuilder) rangeIter(ctx *sql.Context, part sql.Partition) (prolly.MapIter, error) {
   376  	switch p := part.(type) {
   377  	case pointPartition:
   378  		return lb.newPointLookup(ctx, p.r)
   379  	case rangePartition:
   380  		if p.isReverse {
   381  			return lb.sec.IterRangeReverse(ctx, p.prollyRange)
   382  		} else {
   383  			return lb.sec.IterRange(ctx, p.prollyRange)
   384  		}
   385  	default:
   386  		panic(fmt.Sprintf("unexpected prolly partition type: %T", part))
   387  	}
   388  }
   389  
   390  // coveringLookupBuilder constructs row iters for covering lookups,
   391  // where we only need to cursor seek on a single index to both identify
   392  // target keys and fill all requested projections
   393  type coveringLookupBuilder struct {
   394  	*baseLookupBuilder
   395  
   396  	// keyMap transforms secondary index key tuples into SQL tuples.
   397  	// secondary index value tuples are assumed to be empty.
   398  	keyMap, valMap, ordMap val.OrdinalMapping
   399  }
   400  
   401  // NewRowIter implements IndexLookup
   402  func (lb *coveringLookupBuilder) NewRowIter(ctx *sql.Context, part sql.Partition) (sql.RowIter, error) {
   403  	rangeIter, err := lb.rangeIter(ctx, part)
   404  	if err != nil {
   405  		return nil, err
   406  	}
   407  	return prollyCoveringIndexIter{
   408  		idx:         lb.idx,
   409  		indexIter:   rangeIter,
   410  		keyDesc:     lb.secKd,
   411  		valDesc:     lb.secVd,
   412  		keyMap:      lb.keyMap,
   413  		valMap:      lb.valMap,
   414  		ordMap:      lb.ordMap,
   415  		sqlSch:      lb.sch.Schema,
   416  		projections: lb.projections,
   417  		ns:          lb.ns,
   418  	}, nil
   419  }
   420  
   421  // nonCoveringLookupBuilder constructs row iters for non-covering lookups,
   422  // where we need to seek on the secondary table for key identity, and then
   423  // the primary table to fill all requested projections.
   424  type nonCoveringLookupBuilder struct {
   425  	*baseLookupBuilder
   426  
   427  	pri   prolly.Map
   428  	priKd val.TupleDesc
   429  	pkBld *val.TupleBuilder
   430  
   431  	pkMap, keyMap, valMap, ordMap val.OrdinalMapping
   432  }
   433  
   434  // NewRowIter implements IndexLookup
   435  func (lb *nonCoveringLookupBuilder) NewRowIter(ctx *sql.Context, part sql.Partition) (sql.RowIter, error) {
   436  	rangeIter, err := lb.rangeIter(ctx, part)
   437  	if err != nil {
   438  		return nil, err
   439  	}
   440  	return prollyIndexIter{
   441  		idx:         lb.idx,
   442  		indexIter:   rangeIter,
   443  		primary:     lb.pri,
   444  		pkBld:       lb.pkBld,
   445  		pkMap:       lb.pkMap,
   446  		keyMap:      lb.keyMap,
   447  		valMap:      lb.valMap,
   448  		ordMap:      lb.ordMap,
   449  		sqlSch:      lb.sch.Schema,
   450  		projections: lb.projections,
   451  	}, nil
   452  }
   453  
   454  // TODO keylessLookupBuilder should be similar to the non-covering
   455  // index case, where we will need to reference the primary index,
   456  // but can take advantage of point lookup optimizations
   457  type keylessLookupBuilder struct {
   458  	*baseLookupBuilder
   459  	s *durableIndexState
   460  }
   461  
   462  // NewRowIter implements IndexLookup
   463  func (lb *keylessLookupBuilder) NewRowIter(ctx *sql.Context, part sql.Partition) (sql.RowIter, error) {
   464  	var prollyRange prolly.Range
   465  	switch p := part.(type) {
   466  	case rangePartition:
   467  		prollyRange = p.prollyRange
   468  	case pointPartition:
   469  		prollyRange = p.r
   470  	}
   471  	return newProllyKeylessIndexIter(ctx, lb.idx, prollyRange, lb.sch, lb.projections, lb.s.Primary, lb.s.Secondary)
   472  }
   473  
   474  type nomsLookupBuilder struct {
   475  	*baseLookupBuilder
   476  	s *durableIndexState
   477  }
   478  
   479  // NewRowIter implements IndexLookup
   480  func (lb *nomsLookupBuilder) NewRowIter(ctx *sql.Context, part sql.Partition) (sql.RowIter, error) {
   481  	p := part.(rangePartition)
   482  	ranges := []*noms.ReadRange{p.nomsRange}
   483  	return RowIterForNomsRanges(ctx, lb.idx, ranges, lb.projections, lb.s)
   484  }
   485  
   486  // boundsCase determines the case upon which the bounds are tested.
   487  type boundsCase byte
   488  
   489  // For each boundsCase, the first element is the lowerbound and the second element is the upperbound
   490  const (
   491  	boundsCase_infinity_infinity boundsCase = iota
   492  	boundsCase_infinity_lessEquals
   493  	boundsCase_infinity_less
   494  	boundsCase_greaterEquals_infinity
   495  	boundsCase_greaterEquals_lessEquals
   496  	boundsCase_greaterEquals_less
   497  	boundsCase_greater_infinity
   498  	boundsCase_greater_lessEquals
   499  	boundsCase_greater_less
   500  	boundsCase_isNull
   501  )
   502  
   503  // columnBounds are used to compare a given value in the noms row iterator.
   504  type columnBounds struct {
   505  	boundsCase
   506  	lowerbound types.Value
   507  	upperbound types.Value
   508  }
   509  
   510  // nomsRangeCheck is used to compare a tuple against a set of comparisons in the noms row iterator.
   511  type nomsRangeCheck []columnBounds
   512  
   513  var _ noms.InRangeCheck = nomsRangeCheck{}
   514  
   515  // Between returns whether the given types.Value is between the bounds. In addition, this returns if the value is outside
   516  // the bounds and above the upperbound.
   517  func (cb columnBounds) Between(ctx context.Context, vr types.ValueReader, val types.Value) (ok bool, over bool, err error) {
   518  	// Only boundCase_isNull matches NULL values,
   519  	// otherwise we terminate the range scan.
   520  	// This is checked early to bypass unpredictable
   521  	// null type comparisons.
   522  	if val.Kind() == types.NullKind {
   523  		isNullCase := cb.boundsCase == boundsCase_isNull
   524  		return isNullCase, !isNullCase, nil
   525  	}
   526  
   527  	switch cb.boundsCase {
   528  	case boundsCase_infinity_infinity:
   529  		return true, false, nil
   530  	case boundsCase_infinity_lessEquals:
   531  		ok, err := cb.upperbound.Less(ctx, vr.Format(), val)
   532  		if err != nil || ok {
   533  			return false, true, err
   534  		}
   535  	case boundsCase_infinity_less:
   536  		ok, err := val.Less(ctx, vr.Format(), cb.upperbound)
   537  		if err != nil || !ok {
   538  			return false, true, err
   539  		}
   540  	case boundsCase_greaterEquals_infinity:
   541  		ok, err := val.Less(ctx, vr.Format(), cb.lowerbound)
   542  		if err != nil || ok {
   543  			return false, false, err
   544  		}
   545  	case boundsCase_greaterEquals_lessEquals:
   546  		ok, err := val.Less(ctx, vr.Format(), cb.lowerbound)
   547  		if err != nil || ok {
   548  			return false, false, err
   549  		}
   550  		ok, err = cb.upperbound.Less(ctx, vr.Format(), val)
   551  		if err != nil || ok {
   552  			return false, true, err
   553  		}
   554  	case boundsCase_greaterEquals_less:
   555  		ok, err := val.Less(ctx, vr.Format(), cb.lowerbound)
   556  		if err != nil || ok {
   557  			return false, false, err
   558  		}
   559  		ok, err = val.Less(ctx, vr.Format(), cb.upperbound)
   560  		if err != nil || !ok {
   561  			return false, true, err
   562  		}
   563  	case boundsCase_greater_infinity:
   564  		ok, err := cb.lowerbound.Less(ctx, vr.Format(), val)
   565  		if err != nil || !ok {
   566  			return false, false, err
   567  		}
   568  	case boundsCase_greater_lessEquals:
   569  		ok, err := cb.lowerbound.Less(ctx, vr.Format(), val)
   570  		if err != nil || !ok {
   571  			return false, false, err
   572  		}
   573  		ok, err = cb.upperbound.Less(ctx, vr.Format(), val)
   574  		if err != nil || ok {
   575  			return false, true, err
   576  		}
   577  	case boundsCase_greater_less:
   578  		ok, err := cb.lowerbound.Less(ctx, vr.Format(), val)
   579  		if err != nil || !ok {
   580  			return false, false, err
   581  		}
   582  		ok, err = val.Less(ctx, vr.Format(), cb.upperbound)
   583  		if err != nil || !ok {
   584  			return false, true, err
   585  		}
   586  	case boundsCase_isNull:
   587  		// an isNull scan skips non-nulls, but does not terminate
   588  		return false, false, nil
   589  	default:
   590  		return false, false, fmt.Errorf("unknown bounds")
   591  	}
   592  	return true, false, nil
   593  }
   594  
   595  // Equals returns whether the calling columnBounds is equivalent to the given columnBounds.
   596  func (cb columnBounds) Equals(otherBounds columnBounds) bool {
   597  	if cb.boundsCase != otherBounds.boundsCase {
   598  		return false
   599  	}
   600  	if cb.lowerbound == nil || otherBounds.lowerbound == nil {
   601  		if cb.lowerbound != nil || otherBounds.lowerbound != nil {
   602  			return false
   603  		}
   604  	} else if !cb.lowerbound.Equals(otherBounds.lowerbound) {
   605  		return false
   606  	}
   607  	if cb.upperbound == nil || otherBounds.upperbound == nil {
   608  		if cb.upperbound != nil || otherBounds.upperbound != nil {
   609  			return false
   610  		}
   611  	} else if !cb.upperbound.Equals(otherBounds.upperbound) {
   612  		return false
   613  	}
   614  	return true
   615  }
   616  
   617  // Check implements the interface noms.InRangeCheck.
   618  func (nrc nomsRangeCheck) Check(ctx context.Context, vr types.ValueReader, tuple types.Tuple) (valid bool, skip bool, err error) {
   619  	itr := types.TupleItrPool.Get().(*types.TupleIterator)
   620  	defer types.TupleItrPool.Put(itr)
   621  	err = itr.InitForTuple(tuple)
   622  	if err != nil {
   623  		return false, false, err
   624  	}
   625  
   626  	for i := 0; i < len(nrc) && itr.HasMore(); i++ {
   627  		if err := itr.Skip(); err != nil {
   628  			return false, false, err
   629  		}
   630  		_, val, err := itr.Next()
   631  		if err != nil {
   632  			return false, false, err
   633  		}
   634  		if val == nil {
   635  			break
   636  		}
   637  
   638  		ok, over, err := nrc[i].Between(ctx, vr, val)
   639  		if err != nil {
   640  			return false, false, err
   641  		}
   642  		if !ok {
   643  			return i != 0 || !over, true, nil
   644  		}
   645  	}
   646  	return true, false, nil
   647  }
   648  
   649  // Equals returns whether the calling nomsRangeCheck is equivalent to the given nomsRangeCheck.
   650  func (nrc nomsRangeCheck) Equals(otherNrc nomsRangeCheck) bool {
   651  	if len(nrc) != len(otherNrc) {
   652  		return false
   653  	}
   654  	for i := range nrc {
   655  		if !nrc[i].Equals(otherNrc[i]) {
   656  			return false
   657  		}
   658  	}
   659  	return true
   660  }
   661  
   662  type nomsKeyIter interface {
   663  	ReadKey(ctx context.Context) (types.Tuple, error)
   664  }