github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/schema/index.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 schema
    16  
    17  import (
    18  	"context"
    19  	"io"
    20  
    21  	"github.com/dolthub/dolt/go/store/types"
    22  )
    23  
    24  type Index interface {
    25  	// AllTags returns the tags of the columns in the entire index, including the primary keys.
    26  	// If we imagined a dolt index as being a standard dolt table, then the tags would represent the schema columns.
    27  	AllTags() []uint64
    28  	// ColumnNames returns the names of the columns in the index.
    29  	ColumnNames() []string
    30  	// Comment returns the comment that was provided upon index creation.
    31  	Comment() string
    32  	// Count returns the number of indexed columns in this index.
    33  	Count() int
    34  	// DeepEquals returns whether this Index is equivalent to another. This function is similar to Equals, however it
    35  	// does take the table's primary keys into consideration.
    36  	DeepEquals(other Index) bool
    37  	// Equals returns whether this Index is equivalent to another. This does not check for column names, thus those may
    38  	// be renamed and the index equivalence will be preserved. It also does not depend on the table's primary keys.
    39  	Equals(other Index) bool
    40  	// GetColumn returns the column for the given tag and whether the column was found or not.
    41  	GetColumn(tag uint64) (Column, bool)
    42  	// IndexedColumnTags returns the tags of the columns in the index.
    43  	IndexedColumnTags() []uint64
    44  	// IsUnique returns whether the given index has the UNIQUE constraint.
    45  	IsUnique() bool
    46  	// IsSpatial returns whether the given index has the SPATIAL constraint.
    47  	IsSpatial() bool
    48  	// IsFullText returns whether the given index has the FULLTEXT constraint.
    49  	IsFullText() bool
    50  	// IsUserDefined returns whether the given index was created by a user or automatically generated.
    51  	IsUserDefined() bool
    52  	// Name returns the name of the index.
    53  	Name() string
    54  	// PrimaryKeyTags returns the primary keys of the indexed table, in the order that they're stored for that table.
    55  	PrimaryKeyTags() []uint64
    56  	// Schema returns the schema for the internal index map. Can be used for table operations.
    57  	Schema() Schema
    58  	// ToTableTuple returns a tuple that may be used to retrieve the original row from the indexed table when given
    59  	// a full index key (and not a partial index key).
    60  	ToTableTuple(ctx context.Context, fullKey types.Tuple, format *types.NomsBinFormat) (types.Tuple, error)
    61  	// PrefixLengths returns the prefix lengths for the index
    62  	PrefixLengths() []uint16
    63  	// FullTextProperties returns all properties belonging to a Full-Text index.
    64  	FullTextProperties() FullTextProperties
    65  }
    66  
    67  var _ Index = (*indexImpl)(nil)
    68  
    69  type indexImpl struct {
    70  	name          string
    71  	tags          []uint64
    72  	allTags       []uint64
    73  	indexColl     *indexCollectionImpl
    74  	isUnique      bool
    75  	isSpatial     bool
    76  	isFullText    bool
    77  	isUserDefined bool
    78  	comment       string
    79  	prefixLengths []uint16
    80  	fullTextProps FullTextProperties
    81  }
    82  
    83  func NewIndex(name string, tags, allTags []uint64, indexColl IndexCollection, props IndexProperties) Index {
    84  	var indexCollImpl *indexCollectionImpl
    85  	if indexColl != nil {
    86  		indexCollImpl = indexColl.(*indexCollectionImpl)
    87  	}
    88  
    89  	return &indexImpl{
    90  		name:          name,
    91  		tags:          tags,
    92  		allTags:       allTags,
    93  		indexColl:     indexCollImpl,
    94  		isUnique:      props.IsUnique,
    95  		isSpatial:     props.IsSpatial,
    96  		isFullText:    props.IsFullText,
    97  		isUserDefined: props.IsUserDefined,
    98  		comment:       props.Comment,
    99  		fullTextProps: props.FullTextProperties,
   100  	}
   101  }
   102  
   103  // AllTags implements Index.
   104  func (ix *indexImpl) AllTags() []uint64 {
   105  	return ix.allTags
   106  }
   107  
   108  // ColumnNames implements Index.
   109  func (ix *indexImpl) ColumnNames() []string {
   110  	colNames := make([]string, len(ix.tags))
   111  	for i, tag := range ix.tags {
   112  		colNames[i] = ix.indexColl.colColl.TagToCol[tag].Name
   113  	}
   114  	return colNames
   115  }
   116  
   117  // Comment implements Index.
   118  func (ix *indexImpl) Comment() string {
   119  	return ix.comment
   120  }
   121  
   122  // Count implements Index.
   123  func (ix *indexImpl) Count() int {
   124  	return len(ix.tags)
   125  }
   126  
   127  // Equals implements Index.
   128  func (ix *indexImpl) Equals(other Index) bool {
   129  	if ix.Count() != other.Count() {
   130  		return false
   131  	}
   132  
   133  	// we're only interested in columns the index is defined over, not the table's primary keys
   134  	tt := ix.IndexedColumnTags()
   135  	ot := other.IndexedColumnTags()
   136  	for i := range tt {
   137  		if tt[i] != ot[i] {
   138  			return false
   139  		}
   140  	}
   141  
   142  	return ix.IsUnique() == other.IsUnique() &&
   143  		ix.IsSpatial() == other.IsSpatial() &&
   144  		compareUint16Slices(ix.PrefixLengths(), other.PrefixLengths()) &&
   145  		ix.Comment() == other.Comment() &&
   146  		ix.Name() == other.Name()
   147  }
   148  
   149  // DeepEquals implements Index.
   150  func (ix *indexImpl) DeepEquals(other Index) bool {
   151  	if ix.Count() != other.Count() {
   152  		return false
   153  	}
   154  
   155  	// DeepEquals compares all tags used in this index, as well as the tags from the table's primary key
   156  	tt := ix.AllTags()
   157  	ot := other.AllTags()
   158  	for i := range tt {
   159  		if tt[i] != ot[i] {
   160  			return false
   161  		}
   162  	}
   163  
   164  	return ix.IsUnique() == other.IsUnique() &&
   165  		ix.IsSpatial() == other.IsSpatial() &&
   166  		compareUint16Slices(ix.PrefixLengths(), other.PrefixLengths()) &&
   167  		ix.Comment() == other.Comment() &&
   168  		ix.Name() == other.Name()
   169  }
   170  
   171  // compareUint16Slices returns true if |a| and |b| contain the exact same uint16 values, in the same order; otherwise
   172  // it returns false.
   173  func compareUint16Slices(a, b []uint16) bool {
   174  	if len(a) != len(b) {
   175  		return false
   176  	}
   177  
   178  	for i := range a {
   179  		if a[i] != b[i] {
   180  			return false
   181  		}
   182  	}
   183  
   184  	return true
   185  }
   186  
   187  // GetColumn implements Index.
   188  func (ix *indexImpl) GetColumn(tag uint64) (Column, bool) {
   189  	return ix.indexColl.colColl.GetByTag(tag)
   190  }
   191  
   192  // IndexedColumnTags implements Index.
   193  func (ix *indexImpl) IndexedColumnTags() []uint64 {
   194  	return ix.tags
   195  }
   196  
   197  // IsUnique implements Index.
   198  func (ix *indexImpl) IsUnique() bool {
   199  	return ix.isUnique
   200  }
   201  
   202  // IsSpatial implements Index.
   203  func (ix *indexImpl) IsSpatial() bool {
   204  	return ix.isSpatial
   205  }
   206  
   207  // IsFullText implements Index.
   208  func (ix *indexImpl) IsFullText() bool {
   209  	return ix.isFullText
   210  }
   211  
   212  // IsUserDefined implements Index.
   213  func (ix *indexImpl) IsUserDefined() bool {
   214  	return ix.isUserDefined
   215  }
   216  
   217  // Name implements Index.
   218  func (ix *indexImpl) Name() string {
   219  	return ix.name
   220  }
   221  
   222  // PrimaryKeyTags implements Index.
   223  func (ix *indexImpl) PrimaryKeyTags() []uint64 {
   224  	return ix.indexColl.pks
   225  }
   226  
   227  // Schema implements Index.
   228  func (ix *indexImpl) Schema() Schema {
   229  	contentHashedFields := make([]uint64, 0)
   230  	cols := make([]Column, len(ix.allTags))
   231  	for i, tag := range ix.allTags {
   232  		col := ix.indexColl.colColl.TagToCol[tag]
   233  		cols[i] = Column{
   234  			Name:        col.Name,
   235  			Tag:         tag,
   236  			Kind:        col.Kind,
   237  			IsPartOfPK:  true,
   238  			TypeInfo:    col.TypeInfo,
   239  			Constraints: nil,
   240  		}
   241  
   242  		// contentHashedFields is the collection of column tags for columns in a unique index that do
   243  		// not have a prefix length specified and should be stored as a content hash. This information
   244  		// is needed to later identify that an index is using content-hashed encoding.
   245  		prefixLength := uint16(0)
   246  		if len(ix.PrefixLengths()) > i {
   247  			prefixLength = ix.PrefixLengths()[i]
   248  		}
   249  		if ix.IsUnique() && prefixLength == 0 {
   250  			contentHashedFields = append(contentHashedFields, tag)
   251  		}
   252  	}
   253  	allCols := NewColCollection(cols...)
   254  	nonPkCols := NewColCollection()
   255  	return &schemaImpl{
   256  		pkCols:              allCols,
   257  		nonPKCols:           nonPkCols,
   258  		allCols:             allCols,
   259  		indexCollection:     NewIndexCollection(nil, nil),
   260  		checkCollection:     NewCheckCollection(),
   261  		contentHashedFields: contentHashedFields,
   262  	}
   263  }
   264  
   265  // ToTableTuple implements Index.
   266  func (ix *indexImpl) ToTableTuple(ctx context.Context, fullKey types.Tuple, format *types.NomsBinFormat) (types.Tuple, error) {
   267  	pkTags := make(map[uint64]int)
   268  	for i, tag := range ix.indexColl.pks {
   269  		pkTags[tag] = i
   270  	}
   271  	tplItr, err := fullKey.Iterator()
   272  	if err != nil {
   273  		return types.Tuple{}, err
   274  	}
   275  	resVals := make([]types.Value, len(pkTags)*2)
   276  	for {
   277  		_, tag, err := tplItr.NextUint64()
   278  		if err != nil {
   279  			if err == io.EOF {
   280  				break
   281  			}
   282  			return types.Tuple{}, err
   283  		}
   284  		idx, inPK := pkTags[tag]
   285  		if inPK {
   286  			_, valVal, err := tplItr.Next()
   287  			if err != nil {
   288  				return types.Tuple{}, err
   289  			}
   290  			resVals[idx*2] = types.Uint(tag)
   291  			resVals[idx*2+1] = valVal
   292  		} else {
   293  			err := tplItr.Skip()
   294  			if err != nil {
   295  				return types.Tuple{}, err
   296  			}
   297  		}
   298  	}
   299  	return types.NewTuple(format, resVals...)
   300  }
   301  
   302  // PrefixLengths implements Index.
   303  func (ix *indexImpl) PrefixLengths() []uint16 {
   304  	return ix.prefixLengths
   305  }
   306  
   307  // FullTextProperties implements Index.
   308  func (ix *indexImpl) FullTextProperties() FullTextProperties {
   309  	return ix.fullTextProps
   310  }
   311  
   312  // copy returns an exact copy of the calling index.
   313  func (ix *indexImpl) copy() *indexImpl {
   314  	newIx := *ix
   315  	newIx.tags = make([]uint64, len(ix.tags))
   316  	_ = copy(newIx.tags, ix.tags)
   317  	newIx.allTags = make([]uint64, len(ix.allTags))
   318  	_ = copy(newIx.allTags, ix.allTags)
   319  	if len(ix.prefixLengths) > 0 {
   320  		newIx.prefixLengths = make([]uint16, len(ix.prefixLengths))
   321  		_ = copy(newIx.prefixLengths, ix.prefixLengths)
   322  	}
   323  	if len(newIx.fullTextProps.KeyPositions) > 0 {
   324  		newIx.fullTextProps.KeyPositions = make([]uint16, len(ix.fullTextProps.KeyPositions))
   325  		_ = copy(newIx.fullTextProps.KeyPositions, ix.fullTextProps.KeyPositions)
   326  	}
   327  	return &newIx
   328  }