github.com/dolthub/go-mysql-server@v0.18.0/memory/index.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 memory
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  
    21  	"github.com/dolthub/go-mysql-server/sql"
    22  	"github.com/dolthub/go-mysql-server/sql/expression"
    23  	"github.com/dolthub/go-mysql-server/sql/fulltext"
    24  	"github.com/dolthub/go-mysql-server/sql/types"
    25  )
    26  
    27  const CommentPreventingIndexBuilding = "__FOR TESTING: I cannot be built__"
    28  
    29  type Index struct {
    30  	DB         string // required for engine tests with driver
    31  	DriverName string // required for engine tests with driver
    32  	Tbl        *Table // required for engine tests with driver
    33  	TableName  string
    34  	Exprs      []sql.Expression
    35  	Name       string
    36  	Unique     bool
    37  	Spatial    bool
    38  	Fulltext   bool
    39  	CommentStr string
    40  	PrefixLens []uint16
    41  	fulltextInfo
    42  }
    43  
    44  type fulltextInfo struct {
    45  	PositionTableName    string
    46  	DocCountTableName    string
    47  	GlobalCountTableName string
    48  	RowCountTableName    string
    49  	fulltext.KeyColumns
    50  }
    51  
    52  var _ sql.Index = (*Index)(nil)
    53  var _ sql.FilteredIndex = (*Index)(nil)
    54  var _ sql.OrderedIndex = (*Index)(nil)
    55  var _ sql.ExtendedIndex = (*Index)(nil)
    56  var _ fulltext.Index = (*Index)(nil)
    57  
    58  func (idx *Index) Database() string                    { return idx.DB }
    59  func (idx *Index) Driver() string                      { return idx.DriverName }
    60  func (idx *Index) MemTable() *Table                    { return idx.Tbl }
    61  func (idx *Index) ColumnExpressions() []sql.Expression { return idx.Exprs }
    62  func (idx *Index) IsGenerated() bool                   { return false }
    63  
    64  func (idx *Index) Expressions() []string {
    65  	var exprs []string
    66  	for _, e := range idx.Exprs {
    67  		exprs = append(exprs, e.String())
    68  	}
    69  	return exprs
    70  }
    71  
    72  func (idx *Index) ExtendedExpressions() []string {
    73  	var exprs []string
    74  	foundCols := make(map[string]struct{})
    75  	for _, e := range idx.Exprs {
    76  		foundCols[strings.ToLower(e.(*expression.GetField).Name())] = struct{}{}
    77  		exprs = append(exprs, e.String())
    78  	}
    79  	for _, ord := range idx.Tbl.data.schema.PkOrdinals {
    80  		col := idx.Tbl.data.schema.Schema[ord]
    81  		if _, ok := foundCols[strings.ToLower(col.Name)]; !ok {
    82  			exprs = append(exprs, fmt.Sprintf("%s.%s", idx.Tbl.name, col.Name))
    83  		}
    84  	}
    85  	return exprs
    86  }
    87  
    88  // ExtendedExprs returns the same information as ExtendedExpressions, but in sql.Expression form.
    89  func (idx *Index) ExtendedExprs() []sql.Expression {
    90  	var exprs []sql.Expression
    91  	foundCols := make(map[string]struct{})
    92  	for _, e := range idx.Exprs {
    93  		foundCols[strings.ToLower(e.(*expression.GetField).Name())] = struct{}{}
    94  		exprs = append(exprs, e)
    95  	}
    96  	for _, ord := range idx.Tbl.data.schema.PkOrdinals {
    97  		col := idx.Tbl.data.schema.Schema[ord]
    98  		if _, ok := foundCols[strings.ToLower(col.Name)]; !ok {
    99  			exprs = append(exprs, expression.NewGetFieldWithTable(ord, 0, col.Type, idx.DB, idx.Tbl.name, col.Name, col.Nullable))
   100  		}
   101  	}
   102  	return exprs
   103  }
   104  
   105  func (idx *Index) CanSupport(...sql.Range) bool {
   106  	return true
   107  }
   108  
   109  func (idx *Index) IsUnique() bool {
   110  	return idx.Unique
   111  }
   112  
   113  func (idx *Index) IsSpatial() bool {
   114  	return idx.Spatial
   115  }
   116  
   117  func (idx *Index) IsFullText() bool {
   118  	return idx.Fulltext
   119  }
   120  
   121  func (idx *Index) Comment() string {
   122  	return idx.CommentStr
   123  }
   124  
   125  func (idx *Index) PrefixLengths() []uint16 {
   126  	return idx.PrefixLens
   127  }
   128  
   129  func (idx *Index) IndexType() string {
   130  	if len(idx.DriverName) > 0 {
   131  		return idx.DriverName
   132  	}
   133  	return "BTREE" // fake but so are you
   134  }
   135  
   136  func (idx *Index) rowToIndexStorage(row sql.Row, partitionName string, rowIdx int) (sql.Row, error) {
   137  	if idx.Name == "PRIMARY" {
   138  		return row, nil
   139  	}
   140  
   141  	exprs := idx.ExtendedExprs()
   142  	newRow := make(sql.Row, len(exprs)+1)
   143  	for i, expr := range exprs {
   144  		var err error
   145  		newRow[i], err = expr.Eval(nil, row)
   146  		if err != nil {
   147  			return nil, err
   148  		}
   149  	}
   150  	// The final element of the row is the location of the row in the primary table storage slice.
   151  	newRow[len(exprs)] = primaryRowLocation{
   152  		partition: partitionName,
   153  		idx:       rowIdx,
   154  	}
   155  
   156  	return newRow, nil
   157  }
   158  
   159  func (idx *Index) rangeFilterExpr(ctx *sql.Context, ranges ...sql.Range) (sql.Expression, error) {
   160  	if idx.CommentStr == CommentPreventingIndexBuilding {
   161  		return nil, nil
   162  	}
   163  
   164  	return expression.NewRangeFilterExpr(idx.ExtendedExprs(), ranges)
   165  }
   166  
   167  // ColumnExpressionTypes implements the interface sql.Index.
   168  func (idx *Index) ColumnExpressionTypes() []sql.ColumnExpressionType {
   169  	cets := make([]sql.ColumnExpressionType, len(idx.Exprs))
   170  	for i, expr := range idx.Exprs {
   171  		cets[i] = sql.ColumnExpressionType{
   172  			Expression: expr.String(),
   173  			Type:       expr.Type(),
   174  		}
   175  	}
   176  	return cets
   177  }
   178  
   179  func (idx *Index) ExtendedColumnExpressionTypes() []sql.ColumnExpressionType {
   180  	cets := make([]sql.ColumnExpressionType, 0, len(idx.Tbl.data.schema.Schema))
   181  	cetsInExprs := make(map[string]struct{})
   182  	for _, expr := range idx.Exprs {
   183  		cetsInExprs[strings.ToLower(expr.(*expression.GetField).Name())] = struct{}{}
   184  		cets = append(cets, sql.ColumnExpressionType{
   185  			Expression: expr.String(),
   186  			Type:       expr.Type(),
   187  		})
   188  	}
   189  	for _, ord := range idx.Tbl.data.schema.PkOrdinals {
   190  		col := idx.Tbl.data.schema.Schema[ord]
   191  		if _, ok := cetsInExprs[strings.ToLower(col.Name)]; !ok {
   192  			cets = append(cets, sql.ColumnExpressionType{
   193  				Expression: fmt.Sprintf("%s.%s", idx.Tbl.name, col.Name),
   194  				Type:       col.Type,
   195  			})
   196  		}
   197  	}
   198  	return cets
   199  }
   200  
   201  func (idx *Index) FullTextTableNames(ctx *sql.Context) (fulltext.IndexTableNames, error) {
   202  	return fulltext.IndexTableNames{
   203  		Config:      idx.Tbl.data.fullTextConfigTableName,
   204  		Position:    idx.fulltextInfo.PositionTableName,
   205  		DocCount:    idx.fulltextInfo.DocCountTableName,
   206  		GlobalCount: idx.fulltextInfo.GlobalCountTableName,
   207  		RowCount:    idx.fulltextInfo.RowCountTableName,
   208  	}, nil
   209  }
   210  
   211  func (idx *Index) FullTextKeyColumns(ctx *sql.Context) (fulltext.KeyColumns, error) {
   212  	return idx.fulltextInfo.KeyColumns, nil
   213  }
   214  
   215  func (idx *Index) ID() string {
   216  	if len(idx.Name) > 0 {
   217  		return idx.Name
   218  	}
   219  
   220  	if len(idx.Exprs) == 1 {
   221  		return idx.Exprs[0].String()
   222  	}
   223  	var parts = make([]string, len(idx.Exprs))
   224  	for i, e := range idx.Exprs {
   225  		parts[i] = e.String()
   226  	}
   227  
   228  	return "(" + strings.Join(parts, ", ") + ")"
   229  }
   230  
   231  func (idx *Index) Table() string { return idx.TableName }
   232  
   233  func (idx *Index) HandledFilters(filters []sql.Expression) []sql.Expression {
   234  	var handled []sql.Expression
   235  	if idx.Spatial {
   236  		return handled
   237  	}
   238  	for _, expr := range filters {
   239  		if !expression.PreciseComparison(expr) {
   240  			continue
   241  		}
   242  		handled = append(handled, expr)
   243  	}
   244  	return handled
   245  }
   246  
   247  // validateIndexType returns the best comparison type between the two given types, as it takes into consideration
   248  // whether the types contain collations.
   249  func (idx *Index) validateIndexType(valType sql.Type, rangeType sql.Type) sql.Type {
   250  	if _, ok := rangeType.(sql.TypeWithCollation); ok {
   251  		return rangeType.Promote()
   252  	}
   253  	return valType
   254  }
   255  
   256  // ExpressionsIndex is an index made out of one or more expressions (usually field expressions), linked to a Table.
   257  type ExpressionsIndex interface {
   258  	sql.Index
   259  	MemTable() *Table
   260  	ColumnExpressions() []sql.Expression
   261  }
   262  
   263  func (idx *Index) Order() sql.IndexOrder {
   264  	// If there are any hash-encoded fields, then we will not have a deterministic order
   265  	// Even though we don't actually hash hash-encoded fields in the in-memory implementation, we
   266  	// still honor this here so that we can test this behavior.
   267  	if len(idx.contentHashedFields()) > 0 {
   268  		return sql.IndexOrderNone
   269  	}
   270  
   271  	return sql.IndexOrderAsc
   272  }
   273  
   274  func (idx *Index) Reversible() bool {
   275  	// If there are any hash-encoded fields, then we will not have a deterministic order
   276  	// Even though we don't actually hash hash-encoded fields in the in-memory implementation, we
   277  	// still honor this here so that we can test this behavior.
   278  	if len(idx.contentHashedFields()) > 0 {
   279  		return false
   280  	}
   281  
   282  	return true
   283  }
   284  
   285  func (idx Index) copy() *Index {
   286  	return &idx
   287  }
   288  
   289  // columnIndexes returns the indexes in the given schema for the fields in this index
   290  func (idx *Index) columnIndexes(schema sql.Schema) []int {
   291  	indexes := make([]int, len(idx.Exprs))
   292  	for i, expr := range idx.Exprs {
   293  		gf, ok := expr.(*expression.GetField)
   294  		if !ok {
   295  			panic(fmt.Sprintf("expected GetField expression, got %T", expr))
   296  		}
   297  		indexes[i] = schema.IndexOfColName(gf.Name())
   298  	}
   299  	return indexes
   300  }
   301  
   302  // contentHashedFields returns a slice of field indexes in this secondary index that should be hashed, instead
   303  // of directly storing their content. This is only applicable to unique secondary indexes.
   304  func (idx *Index) contentHashedFields() (contentHashedFields []uint) {
   305  	if !idx.Unique {
   306  		return nil
   307  	}
   308  
   309  	for i, expr := range idx.Exprs {
   310  		if !types.IsTextBlob(expr.Type()) {
   311  			continue
   312  		}
   313  
   314  		prefixLength := uint16(0)
   315  		if len(idx.PrefixLens) > i {
   316  			prefixLength = idx.PrefixLens[i]
   317  		}
   318  		if prefixLength == 0 {
   319  			contentHashedFields = append(contentHashedFields, uint(i))
   320  		}
   321  	}
   322  
   323  	return contentHashedFields
   324  }