Files
dolt/go/libraries/doltcore/schema/index.go
T
2021-01-26 17:44:11 -08:00

246 lines
7.1 KiB
Go

// Copyright 2020 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package schema
import (
"context"
"fmt"
"github.com/dolthub/dolt/go/store/types"
)
type Index interface {
// AllTags returns the tags of the columns in the entire index, including the primary keys.
// If we imagined a dolt index as being a standard dolt table, then the tags would represent the schema columns.
AllTags() []uint64
// ColumnNames returns the names of the columns in the index.
ColumnNames() []string
// Comment returns the comment that was provided upon index creation.
Comment() string
// Count returns the number of indexed columns in this index.
Count() int
// Equals returns whether this Index is equivalent to another. This does not check for column names, thus those may
// be renamed and the index equivalence will be preserved. It also does not depend on the table's primary keys.
Equals(other Index) bool
// GetColumn returns the column for the given tag and whether the column was found or not.
GetColumn(tag uint64) (Column, bool)
// IndexedColumnTags returns the tags of the columns in the index.
IndexedColumnTags() []uint64
// IsUnique returns whether the given index has the UNIQUE constraint.
IsUnique() bool
// IsUserDefined returns whether the given index was created by a user or automatically generated.
IsUserDefined() bool
// Name returns the name of the index.
Name() string
// PrimaryKeyTags returns the primary keys of the indexed table, in the order that they're stored for that table.
PrimaryKeyTags() []uint64
// Schema returns the schema for the internal index map. Can be used for table operations.
Schema() Schema
// VerifyMap returns whether the given map iterator contains all valid keys and values for this index.
VerifyMap(ctx context.Context, iter types.MapIterator, nbf *types.NomsBinFormat) error
}
var _ Index = (*indexImpl)(nil)
type indexImpl struct {
name string
tags []uint64
allTags []uint64
indexColl *indexCollectionImpl
isUnique bool
isUserDefined bool
comment string
}
func NewIndex(name string, tags, allTags []uint64, indexColl *indexCollectionImpl, props IndexProperties) Index {
return &indexImpl{
name: name,
tags: tags,
allTags: allTags,
indexColl: indexColl,
isUnique: props.IsUnique,
isUserDefined: props.IsUserDefined,
comment: props.Comment,
}
}
// AllTags implements Index.
func (ix *indexImpl) AllTags() []uint64 {
return ix.allTags
}
// ColumnNames implements Index.
func (ix *indexImpl) ColumnNames() []string {
colNames := make([]string, len(ix.tags))
for i, tag := range ix.tags {
colNames[i] = ix.indexColl.colColl.TagToCol[tag].Name
}
return colNames
}
// Comment implements Index.
func (ix *indexImpl) Comment() string {
return ix.comment
}
// Count implements Index.
func (ix *indexImpl) Count() int {
return len(ix.tags)
}
// Equals implements Index.
func (ix *indexImpl) Equals(other Index) bool {
if ix.Count() != other.Count() {
return false
}
// we're only interested in columns the index is defined over, not the table's primary keys
tt := ix.IndexedColumnTags()
ot := other.IndexedColumnTags()
for i := range tt {
if tt[i] != ot[i] {
return false
}
}
return ix.IsUnique() == other.IsUnique() &&
ix.Comment() == other.Comment() &&
ix.Name() == other.Name()
}
// GetColumn implements Index.
func (ix *indexImpl) GetColumn(tag uint64) (Column, bool) {
return ix.indexColl.colColl.GetByTag(tag)
}
// IndexedColumnTags implements Index.
func (ix *indexImpl) IndexedColumnTags() []uint64 {
return ix.tags
}
// IsUnique implements Index.
func (ix *indexImpl) IsUnique() bool {
return ix.isUnique
}
// IsUserDefined implements Index.
func (ix *indexImpl) IsUserDefined() bool {
return ix.isUserDefined
}
// Name implements Index.
func (ix *indexImpl) Name() string {
return ix.name
}
// PrimaryKeyTags implements Index.
func (ix *indexImpl) PrimaryKeyTags() []uint64 {
return ix.indexColl.pks
}
// Schema implements Index.
func (ix *indexImpl) Schema() Schema {
cols := make([]Column, len(ix.allTags))
for i, tag := range ix.allTags {
col := ix.indexColl.colColl.TagToCol[tag]
cols[i] = Column{
Name: col.Name,
Tag: tag,
Kind: col.Kind,
IsPartOfPK: true,
TypeInfo: col.TypeInfo,
Constraints: nil,
}
}
allCols := NewColCollection(cols...)
nonPkCols := NewColCollection()
return &schemaImpl{
pkCols: allCols,
nonPKCols: nonPkCols,
allCols: allCols,
indexCollection: NewIndexCollection(nil),
}
}
// VerifyMap implements Index.
func (ix *indexImpl) VerifyMap(ctx context.Context, iter types.MapIterator, nbf *types.NomsBinFormat) error {
lastKey := types.EmptyTuple(nbf)
var keyVal types.Value
var valVal types.Value
expectedVal := types.EmptyTuple(nbf)
var err error
cols := make([]Column, len(ix.allTags))
for i, tag := range ix.allTags {
var ok bool
cols[i], ok = ix.indexColl.colColl.TagToCol[tag]
if !ok {
return fmt.Errorf("index `%s` has column with tag `%d` which cannot be found", ix.name, tag)
}
}
for keyVal, valVal, err = iter.Next(ctx); err == nil && keyVal != nil; keyVal, valVal, err = iter.Next(ctx) {
key := keyVal.(types.Tuple)
i := 0
hasNull := false
if key.Len() != uint64(2*len(cols)) {
return fmt.Errorf("mismatched value count in key tuple compared to what index `%s` expects", ix.name)
}
err = key.WalkValues(ctx, func(v types.Value) error {
colIndex := i / 2
isTag := i%2 == 0
if isTag {
if !v.Equals(types.Uint(cols[colIndex].Tag)) {
return fmt.Errorf("column order of map does not match what index `%s` expects", ix.name)
}
} else {
if types.IsNull(v) {
hasNull = true
} else if v.Kind() != cols[colIndex].TypeInfo.NomsKind() {
return fmt.Errorf("column value in map does not match what index `%s` expects", ix.name)
}
}
i++
return nil
})
if err != nil {
return err
}
if ix.isUnique && !hasNull {
partialKeysEqual, err := key.PrefixEquals(ctx, lastKey, uint64(len(ix.tags)*2))
if err != nil {
return err
}
if partialKeysEqual {
return fmt.Errorf("UNIQUE constraint violation while verifying index: %s", ix.name)
}
}
if !expectedVal.Equals(valVal) {
return fmt.Errorf("index map value should be empty")
}
lastKey = key
}
return err
}
// copy returns an exact copy of the calling index.
func (ix *indexImpl) copy() *indexImpl {
newIx := *ix
newIx.tags = make([]uint64, len(ix.tags))
_ = copy(newIx.tags, ix.tags)
newIx.allTags = make([]uint64, len(ix.allTags))
_ = copy(newIx.allTags, ix.allTags)
return &newIx
}