github.com/dolthub/go-mysql-server@v0.18.0/sql/plan/indexed_table_access.go (about)

     1  // Copyright 2020-2021 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 plan
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  
    21  	"gopkg.in/src-d/go-errors.v1"
    22  
    23  	"github.com/dolthub/go-mysql-server/sql"
    24  )
    25  
    26  type itaType uint8
    27  
    28  const (
    29  	ItaTypeStatic itaType = iota
    30  	ItaTypeLookup
    31  )
    32  
    33  var ErrInvalidLookupForIndexedTable = errors.NewKind("indexable table does not support given lookup: %s")
    34  
    35  // IndexedTableAccess represents an indexed lookup of a particular plan.TableNode. The values for the key used to access
    36  // the indexed table is provided in RowIter(), or during static analysis.
    37  type IndexedTableAccess struct {
    38  	TableNode sql.TableNode
    39  	lb        *LookupBuilder
    40  	lookup    sql.IndexLookup
    41  	Table     sql.IndexedTable
    42  	Typ       itaType
    43  	id        sql.TableId
    44  	cols      sql.ColSet
    45  }
    46  
    47  var _ sql.Table = (*IndexedTableAccess)(nil)
    48  var _ sql.Node = (*IndexedTableAccess)(nil)
    49  var _ sql.Nameable = (*IndexedTableAccess)(nil)
    50  var _ sql.Expressioner = (*IndexedTableAccess)(nil)
    51  var _ sql.CollationCoercible = (*IndexedTableAccess)(nil)
    52  var _ sql.TableNode = (*IndexedTableAccess)(nil)
    53  
    54  // NewIndexedAccessForTableNode creates an IndexedTableAccess node if the resolved table embeds
    55  // an IndexAddressableTable, otherwise returns an error.
    56  func NewIndexedAccessForTableNode(node sql.TableNode, lb *LookupBuilder) (*IndexedTableAccess, error) {
    57  	var table = node.UnderlyingTable()
    58  	iaTable, ok := table.(sql.IndexAddressableTable)
    59  	if !ok {
    60  		return nil, fmt.Errorf("table is not index addressable: %s", table.Name())
    61  	}
    62  
    63  	lookup, err := lb.GetLookup(lb.GetZeroKey())
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  	if !lookup.Index.CanSupport(lookup.Ranges...) {
    68  		return nil, ErrInvalidLookupForIndexedTable.New(lookup.Ranges.DebugString())
    69  	}
    70  	var indexedTable sql.IndexedTable
    71  	indexedTable = iaTable.IndexedAccess(lookup)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	if mtn, ok := node.(sql.MutableTableNode); ok {
    77  		mtn, err = mtn.WithTable(indexedTable)
    78  		if err != nil {
    79  			return nil, err
    80  		}
    81  
    82  		indexedTable, ok = mtn.WrappedTable().(sql.IndexedTable)
    83  		if !ok {
    84  			return nil, fmt.Errorf("table is not index addressable: %s", table.Name())
    85  		}
    86  
    87  		node = mtn
    88  	}
    89  
    90  	var id sql.TableId
    91  	var cols sql.ColSet
    92  	if tin, ok := node.(TableIdNode); ok {
    93  		id = tin.Id()
    94  		cols = tin.Columns()
    95  	}
    96  
    97  	return &IndexedTableAccess{
    98  		TableNode: node,
    99  		lb:        lb,
   100  		Table:     indexedTable,
   101  		Typ:       ItaTypeLookup,
   102  		id:        id,
   103  		cols:      cols,
   104  	}, nil
   105  }
   106  
   107  // NewStaticIndexedAccessForTableNode creates an IndexedTableAccess node if the resolved table embeds
   108  // an IndexAddressableTable, otherwise returns an error.
   109  func NewStaticIndexedAccessForTableNode(node sql.TableNode, lookup sql.IndexLookup) (*IndexedTableAccess, error) {
   110  	var table sql.Table
   111  	table = node.UnderlyingTable()
   112  	iaTable, ok := table.(sql.IndexAddressableTable)
   113  	if !ok {
   114  		return nil, fmt.Errorf("table is not index addressable: %s", table.Name())
   115  	}
   116  
   117  	if !lookup.Index.CanSupport(lookup.Ranges...) {
   118  		return nil, ErrInvalidLookupForIndexedTable.New(lookup.Ranges.DebugString())
   119  	}
   120  	indexedTable := iaTable.IndexedAccess(lookup)
   121  
   122  	if mtn, ok := node.(sql.MutableTableNode); ok {
   123  		var err error
   124  		mtn, err = mtn.WithTable(indexedTable)
   125  		if err != nil {
   126  			return nil, err
   127  		}
   128  
   129  		indexedTable, ok = mtn.WrappedTable().(sql.IndexedTable)
   130  		if !ok {
   131  			return nil, fmt.Errorf("table is not index addressable: %s", table.Name())
   132  		}
   133  
   134  		node = mtn
   135  	}
   136  
   137  	var id sql.TableId
   138  	var cols sql.ColSet
   139  	if tin, ok := node.(TableIdNode); ok {
   140  		id = tin.Id()
   141  		cols = tin.Columns()
   142  	}
   143  
   144  	return &IndexedTableAccess{
   145  		TableNode: node,
   146  		lookup:    lookup,
   147  		Table:     indexedTable,
   148  		Typ:       ItaTypeStatic,
   149  		id:        id,
   150  		cols:      cols,
   151  	}, nil
   152  }
   153  
   154  // NewStaticIndexedAccessForFullTextTable creates an IndexedTableAccess node for Full-Text tables, which have a
   155  // different behavior compared to other indexed tables.
   156  func NewStaticIndexedAccessForFullTextTable(node sql.TableNode, lookup sql.IndexLookup, ftTable sql.IndexedTable) *IndexedTableAccess {
   157  	return &IndexedTableAccess{
   158  		TableNode: node,
   159  		lookup:    lookup,
   160  		Table:     ftTable,
   161  		Typ:       ItaTypeStatic,
   162  	}
   163  }
   164  
   165  func (i *IndexedTableAccess) WithDatabase(database sql.Database) (sql.Node, error) {
   166  	return i, nil
   167  }
   168  
   169  func (i *IndexedTableAccess) UnderlyingTable() sql.Table {
   170  	return i.TableNode.UnderlyingTable()
   171  }
   172  
   173  // WithId implements sql.TableIdNode
   174  func (i *IndexedTableAccess) WithId(id sql.TableId) TableIdNode {
   175  	ret := *i
   176  	ret.id = id
   177  	return &ret
   178  }
   179  
   180  // Id implements sql.TableIdNode
   181  func (i *IndexedTableAccess) Id() sql.TableId {
   182  	return i.id
   183  }
   184  
   185  // WithColumns implements sql.TableIdNode
   186  func (i *IndexedTableAccess) WithColumns(set sql.ColSet) TableIdNode {
   187  	ret := *i
   188  	ret.cols = set
   189  	return &ret
   190  }
   191  
   192  // Columns implements sql.TableIdNode
   193  func (i *IndexedTableAccess) Columns() sql.ColSet {
   194  	return i.cols
   195  }
   196  
   197  func (i *IndexedTableAccess) IsStatic() bool {
   198  	return !i.lookup.IsEmpty()
   199  }
   200  
   201  func (i *IndexedTableAccess) Resolved() bool {
   202  	return i.TableNode.Resolved()
   203  }
   204  
   205  func (i *IndexedTableAccess) IsReadOnly() bool {
   206  	return true
   207  }
   208  
   209  func (i *IndexedTableAccess) Schema() sql.Schema {
   210  	return i.TableNode.Schema()
   211  }
   212  
   213  func (i *IndexedTableAccess) Collation() sql.CollationID {
   214  	return i.TableNode.Collation()
   215  }
   216  
   217  func (i *IndexedTableAccess) Comment() string {
   218  	if ct, ok := i.Table.(sql.CommentedTable); ok {
   219  		return ct.Comment()
   220  	}
   221  	return ""
   222  }
   223  
   224  func (i *IndexedTableAccess) Children() []sql.Node {
   225  	return nil
   226  }
   227  
   228  func (i *IndexedTableAccess) WithChildren(children ...sql.Node) (sql.Node, error) {
   229  	if len(children) != 0 {
   230  		return nil, sql.ErrInvalidChildrenNumber.New(i, len(children), 0)
   231  	}
   232  
   233  	return i, nil
   234  }
   235  
   236  func (i *IndexedTableAccess) Name() string {
   237  	return i.TableNode.Name()
   238  }
   239  
   240  func (i *IndexedTableAccess) WithName(s string) sql.Node {
   241  	ret := *i
   242  	ret.TableNode = i.TableNode.(sql.RenameableNode).WithName(s).(sql.TableNode)
   243  	return &ret
   244  }
   245  
   246  func (i *IndexedTableAccess) Database() sql.Database {
   247  	return i.TableNode.Database()
   248  }
   249  
   250  func (i *IndexedTableAccess) CheckPrivileges(ctx *sql.Context, opChecker sql.PrivilegedOperationChecker) bool {
   251  	return i.TableNode.CheckPrivileges(ctx, opChecker)
   252  }
   253  
   254  // CollationCoercibility implements the interface sql.CollationCoercible.
   255  func (i *IndexedTableAccess) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
   256  	return i.TableNode.CollationCoercibility(ctx)
   257  }
   258  
   259  func (i *IndexedTableAccess) Index() sql.Index {
   260  	if !i.lookup.IsEmpty() {
   261  		return i.lookup.Index
   262  	}
   263  	return i.lb.index
   264  }
   265  
   266  // CanBuildIndex returns whether an index lookup on this table can be successfully built for a zero-valued key. For a
   267  // static lookup, no lookup needs to be built, so returns true.
   268  func (i *IndexedTableAccess) CanBuildIndex(ctx *sql.Context) (bool, error) {
   269  	// If the lookup was provided at analysis time (static evaluation), then an index was already built
   270  	if !i.lookup.IsEmpty() {
   271  		return true, nil
   272  	}
   273  
   274  	key := i.lb.GetZeroKey()
   275  	lookup, err := i.lb.GetLookup(key)
   276  	return err == nil && !lookup.IsEmpty(), nil
   277  }
   278  
   279  func (i *IndexedTableAccess) GetLookup(ctx *sql.Context, row sql.Row) (sql.IndexLookup, error) {
   280  	// if the lookup was provided at analysis time (static evaluation), use it.
   281  	if !i.lookup.IsEmpty() {
   282  		return i.lookup, nil
   283  	}
   284  
   285  	key, err := i.lb.GetKey(ctx, row)
   286  	if err != nil {
   287  		return sql.IndexLookup{}, err
   288  	}
   289  	return i.lb.GetLookup(key)
   290  }
   291  
   292  func (i *IndexedTableAccess) getLookup2(ctx *sql.Context, row sql.Row2) (sql.IndexLookup, error) {
   293  	// if the lookup was provided at analysis time (static evaluation), use it.
   294  	if !i.lookup.IsEmpty() {
   295  		return i.lookup, nil
   296  	}
   297  
   298  	key, err := i.lb.GetKey2(ctx, row)
   299  	if err != nil {
   300  		return sql.IndexLookup{}, err
   301  	}
   302  	return i.lb.GetLookup(key)
   303  }
   304  
   305  func (i *IndexedTableAccess) String() string {
   306  	pr := sql.NewTreePrinter()
   307  	pr.WriteNode("IndexedTableAccess(%s)", i.TableNode.Name())
   308  	var children []string
   309  	children = append(children, fmt.Sprintf("index: %s", formatIndexDecoratorString(i.Index())))
   310  	if !i.lookup.IsEmpty() {
   311  		children = append(children, fmt.Sprintf("filters: %s", i.lookup.Ranges.DebugString()))
   312  	}
   313  
   314  	if pt, ok := i.Table.(sql.ProjectedTable); ok {
   315  		projections := pt.Projections()
   316  		if projections != nil {
   317  			columns := make([]string, len(projections))
   318  			for i, c := range projections {
   319  				columns[i] = strings.ToLower(c)
   320  			}
   321  			children = append(children, fmt.Sprintf("columns: %v", columns))
   322  		}
   323  	}
   324  
   325  	if i.lb != nil && len(i.lb.keyExprs) > 0 {
   326  		keys := make([]string, len(i.lb.keyExprs))
   327  		for i, e := range i.lb.keyExprs {
   328  			keys[i] = e.String()
   329  		}
   330  		children = append(children, fmt.Sprintf("keys: %s", strings.Join(keys, ", ")))
   331  	}
   332  
   333  	if ft, ok := i.Table.(sql.FilteredTable); ok {
   334  		var filters []string
   335  		for _, f := range ft.Filters() {
   336  			filters = append(filters, f.String())
   337  		}
   338  		if len(filters) > 0 {
   339  			pr.WriteChildren(fmt.Sprintf("filters: %v", filters))
   340  		}
   341  	}
   342  
   343  	if i.lookup.IsReverse {
   344  		children = append(children, fmt.Sprintf("reverse: %v", i.lookup.IsReverse))
   345  	}
   346  
   347  	pr.WriteChildren(children...)
   348  	return pr.String()
   349  }
   350  
   351  func formatIndexDecoratorString(idx sql.Index) string {
   352  	var expStrs []string
   353  	expStrs = append(expStrs, idx.Expressions()...)
   354  	return fmt.Sprintf("[%s]", strings.Join(expStrs, ","))
   355  }
   356  
   357  func (i *IndexedTableAccess) DebugString() string {
   358  	pr := sql.NewTreePrinter()
   359  	pr.WriteNode("IndexedTableAccess(%s)", i.TableNode.Name())
   360  	var children []string
   361  	children = append(children, fmt.Sprintf("index: %s", formatIndexDecoratorString(i.Index())))
   362  	if !i.lookup.IsEmpty() {
   363  		children = append(children, fmt.Sprintf("static: %s", i.lookup.Ranges.DebugString()))
   364  		if i.lookup.IsReverse {
   365  			children = append(children, fmt.Sprintf("reverse: %v", i.lookup.IsReverse))
   366  		}
   367  	} else {
   368  		var filters []string
   369  		for _, e := range i.lb.keyExprs {
   370  			filters = append(filters, sql.DebugString(e))
   371  		}
   372  		if len(filters) > 0 {
   373  			children = append(children, fmt.Sprintf(fmt.Sprintf("keys: %v", filters)))
   374  		}
   375  	}
   376  
   377  	children = append(children, fmt.Sprintf("colSet: %s", i.Columns()), fmt.Sprintf("tableId: %d", i.Id()))
   378  
   379  	// TableWrappers may want to print their own debug info
   380  	if wrapper, ok := i.Table.(sql.TableWrapper); ok {
   381  		if ds, ok := wrapper.(sql.DebugStringer); ok {
   382  			children = append(children, sql.DebugString(ds))
   383  		}
   384  	} else {
   385  		children = append(children, TableDebugString(i.Table))
   386  	}
   387  
   388  	pr.WriteChildren(children...)
   389  	return pr.String()
   390  }
   391  
   392  // Expressions implements sql.Expressioner
   393  func (i *IndexedTableAccess) Expressions() []sql.Expression {
   394  	if !i.lookup.IsEmpty() {
   395  		return nil
   396  	}
   397  	return i.lb.Expressions()
   398  }
   399  
   400  func (i *IndexedTableAccess) NullMask() []bool {
   401  	if !i.lookup.IsEmpty() {
   402  		return nil
   403  	}
   404  	return i.lb.matchesNullMask
   405  }
   406  
   407  // WithExpressions implements sql.Expressioner
   408  func (i *IndexedTableAccess) WithExpressions(exprs ...sql.Expression) (sql.Node, error) {
   409  	if !i.lookup.IsEmpty() {
   410  		if len(exprs) != 0 {
   411  			return nil, sql.ErrInvalidChildrenNumber.New(i, len(exprs), 0)
   412  		}
   413  		n := *i
   414  		return &n, nil
   415  	}
   416  	lb, err := i.lb.WithExpressions(i, exprs...)
   417  	if err != nil {
   418  		return nil, err
   419  	}
   420  	ret := *i
   421  	ret.lb = lb
   422  	return &ret, nil
   423  }
   424  
   425  func (i IndexedTableAccess) WithTable(table sql.IndexedTable) (sql.Node, error) {
   426  	i.Table = table
   427  	return &i, nil
   428  }
   429  
   430  // Partitions implements sql.Table
   431  func (i *IndexedTableAccess) Partitions(ctx *sql.Context) (sql.PartitionIter, error) {
   432  	return i.Table.LookupPartitions(ctx, i.lookup)
   433  }
   434  
   435  // PartitionRows implements sql.Table
   436  func (i *IndexedTableAccess) PartitionRows(ctx *sql.Context, partition sql.Partition) (sql.RowIter, error) {
   437  	return i.Table.PartitionRows(ctx, partition)
   438  }
   439  
   440  // GetIndexLookup returns the sql.IndexLookup from an IndexedTableAccess.
   441  // This method is exported for use in integration tests.
   442  func GetIndexLookup(ita *IndexedTableAccess) sql.IndexLookup {
   443  	return ita.lookup
   444  }
   445  
   446  type lookupBuilderKey []interface{}
   447  
   448  // LookupBuilder abstracts secondary table access for an LookupJoin.
   449  // A row from the primary table is first evaluated on the secondary index's
   450  // expressions (columns) to produce a lookupBuilderKey. Consider the
   451  // query below, assuming B has an index `xy (x,y)`:
   452  //
   453  // select * from A join B on a.x = b.x AND a.y = b.y
   454  //
   455  // Assume we choose A as the primary row source and B as a secondary lookup
   456  // on `xy`. For every row in A, we will produce a lookupBuilderKey on B
   457  // using the join condition. For the A row (x=1,y=2), the lookup key into B
   458  // will be (1,2) to reflect the B-xy index access.
   459  //
   460  // Then we construct a sql.RangeCollection to represent the (1,2) point
   461  // lookup into B-xy. The collection will always be a single range, because
   462  // a point lookup cannot be a disjoint set of ranges. The range will also
   463  // have the same dimension as the index itself. If the join condition is
   464  // a partial prefix on the index (ex: INDEX x (x)), the unfiltered columns
   465  // are padded.
   466  //
   467  // The <=> filter is a special case for two reasons. 1) It is not a point
   468  // lookup, the corresponding range will either be IsNull or IsNotNull
   469  // depending on whether the primary row key column is nil or not,
   470  // respectfully. 2) The format of the output range is variable, while
   471  // equality ranges are identical except for bound values.
   472  //
   473  // Currently the analyzer constructs one of these and uses it for the
   474  // IndexedTableAccess nodes below an indexed join, for example. This struct is
   475  // also used to implement Expressioner on the IndexedTableAccess node.
   476  type LookupBuilder struct {
   477  	keyExprs  []sql.Expression
   478  	keyExprs2 []sql.Expression2
   479  
   480  	// When building the lookup, we will use an IndexBuilder. If the
   481  	// extracted lookup value is NULL, but we have a non-NULL safe
   482  	// comparison, then the lookup should return no values. But if the
   483  	// comparison is NULL-safe, then the lookup should returns indexed
   484  	// values having that value <=> NULL. For each |keyExpr|, this field
   485  	// contains |true| if the lookup should also match NULLs, and |false|
   486  	// otherwise.
   487  	matchesNullMask []bool
   488  
   489  	index sql.Index
   490  
   491  	key           lookupBuilderKey
   492  	rang          sql.Range
   493  	nullSafe      bool
   494  	isPointLookup bool
   495  	emptyRange    bool
   496  	cets          []sql.ColumnExpressionType
   497  }
   498  
   499  func NewLookupBuilder(index sql.Index, keyExprs []sql.Expression, matchesNullMask []bool) *LookupBuilder {
   500  	cets := index.ColumnExpressionTypes()
   501  	var nullSafe = true
   502  	for i := range matchesNullMask {
   503  		if matchesNullMask[i] {
   504  			nullSafe = false
   505  		}
   506  	}
   507  	return &LookupBuilder{
   508  		index:           index,
   509  		keyExprs:        keyExprs,
   510  		matchesNullMask: matchesNullMask,
   511  		cets:            cets,
   512  		nullSafe:        nullSafe,
   513  		isPointLookup:   true,
   514  	}
   515  }
   516  
   517  func (lb *LookupBuilder) initializeRange(key lookupBuilderKey) {
   518  	lb.rang = make(sql.Range, len(lb.cets))
   519  	lb.emptyRange = false
   520  	lb.isPointLookup = len(key) == len(lb.cets)
   521  	var i int
   522  	for i < len(key) {
   523  		if key[i] == nil {
   524  			lb.emptyRange = true
   525  			lb.isPointLookup = false
   526  		}
   527  		if lb.matchesNullMask[i] {
   528  			if key[i] == nil {
   529  				lb.rang[i] = sql.NullRangeColumnExpr(lb.cets[i].Type)
   530  
   531  			} else {
   532  				lb.rang[i] = sql.NotNullRangeColumnExpr(lb.cets[i].Type)
   533  			}
   534  		} else {
   535  			lb.rang[i] = sql.ClosedRangeColumnExpr(key[i], key[i], lb.cets[i].Type)
   536  		}
   537  		i++
   538  	}
   539  	for i < len(lb.cets) {
   540  		lb.rang[i] = sql.AllRangeColumnExpr(lb.cets[i].Type)
   541  		lb.isPointLookup = false
   542  		i++
   543  	}
   544  	return
   545  }
   546  
   547  func (lb *LookupBuilder) GetLookup(key lookupBuilderKey) (sql.IndexLookup, error) {
   548  	if lb.rang == nil {
   549  		lb.initializeRange(key)
   550  		return sql.IndexLookup{
   551  			Index:           lb.index,
   552  			Ranges:          []sql.Range{lb.rang},
   553  			IsPointLookup:   lb.nullSafe && lb.isPointLookup && lb.index.IsUnique(),
   554  			IsEmptyRange:    lb.emptyRange,
   555  			IsSpatialLookup: false,
   556  		}, nil
   557  	}
   558  
   559  	lb.emptyRange = false
   560  	lb.isPointLookup = len(key) == len(lb.cets)
   561  	for i := range key {
   562  		if key[i] == nil {
   563  			lb.emptyRange = true
   564  			lb.isPointLookup = false
   565  		}
   566  		if lb.matchesNullMask[i] {
   567  			if key[i] == nil {
   568  				lb.rang[i] = sql.NullRangeColumnExpr(lb.cets[i].Type)
   569  			} else {
   570  				k, _, err := lb.rang[i].Typ.Convert(key[i])
   571  				if err != nil {
   572  					// TODO: throw warning, and this should truncate for strings
   573  					err = nil
   574  					k = lb.rang[i].Typ.Zero()
   575  				}
   576  				lb.rang[i].LowerBound = sql.Below{Key: k}
   577  				lb.rang[i].UpperBound = sql.Above{Key: k}
   578  			}
   579  		} else {
   580  			k, _, err := lb.rang[i].Typ.Convert(key[i])
   581  			if err != nil {
   582  				// TODO: throw warning, and this should truncate for strings
   583  				err = nil
   584  				k = lb.rang[i].Typ.Zero()
   585  			}
   586  			lb.rang[i].LowerBound = sql.Below{Key: k}
   587  			lb.rang[i].UpperBound = sql.Above{Key: k}
   588  		}
   589  	}
   590  
   591  	return sql.IndexLookup{
   592  		Index:           lb.index,
   593  		Ranges:          []sql.Range{lb.rang},
   594  		IsPointLookup:   lb.nullSafe && lb.isPointLookup && lb.index.IsUnique(),
   595  		IsEmptyRange:    lb.emptyRange,
   596  		IsSpatialLookup: false,
   597  	}, nil
   598  }
   599  
   600  func (lb *LookupBuilder) GetKey(ctx *sql.Context, row sql.Row) (lookupBuilderKey, error) {
   601  	if lb.key == nil {
   602  		lb.key = make([]interface{}, len(lb.keyExprs))
   603  	}
   604  	for i := range lb.keyExprs {
   605  		var err error
   606  		lb.key[i], err = lb.keyExprs[i].Eval(ctx, row)
   607  		if err != nil {
   608  			return nil, err
   609  		}
   610  	}
   611  	return lb.key, nil
   612  }
   613  
   614  func (lb *LookupBuilder) GetKey2(ctx *sql.Context, row sql.Row2) (lookupBuilderKey, error) {
   615  	if lb.key == nil {
   616  		lb.key = make([]interface{}, len(lb.keyExprs))
   617  	}
   618  	for i := range lb.keyExprs {
   619  		var err error
   620  		lb.key[i], err = lb.keyExprs2[i].Eval2(ctx, row)
   621  		if err != nil {
   622  			return nil, err
   623  		}
   624  	}
   625  	return lb.key, nil
   626  }
   627  
   628  func (lb *LookupBuilder) GetZeroKey() lookupBuilderKey {
   629  	key := make(lookupBuilderKey, len(lb.keyExprs))
   630  	for i, keyExpr := range lb.keyExprs {
   631  		key[i] = keyExpr.Type().Zero()
   632  	}
   633  	return key
   634  }
   635  
   636  func (lb *LookupBuilder) Index() sql.Index {
   637  	return lb.index
   638  }
   639  
   640  func (lb *LookupBuilder) Expressions() []sql.Expression {
   641  	return lb.keyExprs
   642  }
   643  
   644  func (lb *LookupBuilder) DebugString() string {
   645  	keyExprs := make([]string, len(lb.keyExprs))
   646  	for i := range lb.keyExprs {
   647  		keyExprs[i] = sql.DebugString(lb.keyExprs[i])
   648  	}
   649  	return fmt.Sprintf("on %s, using fields %s", formatIndexDecoratorString(lb.Index()), strings.Join(keyExprs, ", "))
   650  }
   651  
   652  func (lb *LookupBuilder) WithExpressions(node sql.Node, exprs ...sql.Expression) (*LookupBuilder, error) {
   653  	if len(exprs) != len(lb.keyExprs) {
   654  		return &LookupBuilder{}, sql.ErrInvalidChildrenNumber.New(node, len(exprs), len(lb.keyExprs))
   655  	}
   656  	ret := *lb
   657  	ret.keyExprs = exprs
   658  	return &ret, nil
   659  }