github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/doltdb/foreign_key_coll.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 doltdb
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"encoding/binary"
    21  	"fmt"
    22  	"io"
    23  	"sort"
    24  	"strings"
    25  
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/row"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/rowconv"
    28  	"github.com/dolthub/dolt/go/libraries/doltcore/table/typed/noms"
    29  
    30  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    31  	"github.com/dolthub/dolt/go/libraries/utils/set"
    32  	"github.com/dolthub/dolt/go/store/hash"
    33  	"github.com/dolthub/dolt/go/store/marshal"
    34  	"github.com/dolthub/dolt/go/store/types"
    35  )
    36  
    37  type ForeignKeyCollection struct {
    38  	foreignKeys map[string]ForeignKey
    39  }
    40  
    41  type ForeignKeyReferenceOption byte
    42  
    43  const (
    44  	ForeignKeyReferenceOption_DefaultAction ForeignKeyReferenceOption = iota
    45  	ForeignKeyReferenceOption_Cascade
    46  	ForeignKeyReferenceOption_NoAction
    47  	ForeignKeyReferenceOption_Restrict
    48  	ForeignKeyReferenceOption_SetNull
    49  )
    50  
    51  // ForeignKey is the complete, internal representation of a Foreign Key.
    52  type ForeignKey struct {
    53  	// TODO: remove index names and retrieve indexes by column tags
    54  	Name                   string                    `noms:"name" json:"name"`
    55  	TableName              string                    `noms:"tbl_name" json:"tbl_name"`
    56  	TableIndex             string                    `noms:"tbl_index" json:"tbl_index"`
    57  	TableColumns           []uint64                  `noms:"tbl_cols" json:"tbl_cols"`
    58  	ReferencedTableName    string                    `noms:"ref_tbl_name" json:"ref_tbl_name"`
    59  	ReferencedTableIndex   string                    `noms:"ref_tbl_index" json:"ref_tbl_index"`
    60  	ReferencedTableColumns []uint64                  `noms:"ref_tbl_cols" json:"ref_tbl_cols"`
    61  	OnUpdate               ForeignKeyReferenceOption `noms:"on_update" json:"on_update"`
    62  	OnDelete               ForeignKeyReferenceOption `noms:"on_delete" json:"on_delete"`
    63  }
    64  
    65  // EqualDefs returns whether two foreign keys have the same definition over the same column sets.
    66  // It does not compare table names or foreign key names.
    67  func (fk ForeignKey) EqualDefs(other ForeignKey) bool {
    68  	if len(fk.TableColumns) != len(other.TableColumns) || len(fk.ReferencedTableColumns) != len(other.ReferencedTableColumns) {
    69  		return false
    70  	}
    71  	for i := range fk.TableColumns {
    72  		if fk.TableColumns[i] != other.TableColumns[i] {
    73  			return false
    74  		}
    75  	}
    76  	for i := range fk.ReferencedTableColumns {
    77  		if fk.ReferencedTableColumns[i] != other.ReferencedTableColumns[i] {
    78  			return false
    79  		}
    80  	}
    81  	return fk.Name == other.Name &&
    82  		fk.OnUpdate == other.OnUpdate &&
    83  		fk.OnDelete == other.OnDelete
    84  }
    85  
    86  // DeepEquals compares all attributes of a foreign key to another, including name and table names.
    87  func (fk ForeignKey) DeepEquals(other ForeignKey) bool {
    88  	if !fk.EqualDefs(other) {
    89  		return false
    90  	}
    91  	return fk.Name == other.Name &&
    92  		fk.TableName == other.TableName &&
    93  		fk.ReferencedTableName == other.ReferencedTableName
    94  }
    95  
    96  // HashOf returns the Noms hash of a ForeignKey.
    97  func (fk ForeignKey) HashOf() hash.Hash {
    98  	var bb bytes.Buffer
    99  	bb.Write([]byte(fk.Name))
   100  	bb.Write([]byte(fk.TableName))
   101  	bb.Write([]byte(fk.TableIndex))
   102  	for _, t := range fk.TableColumns {
   103  		_ = binary.Write(&bb, binary.LittleEndian, t)
   104  	}
   105  	bb.Write([]byte(fk.ReferencedTableName))
   106  	bb.Write([]byte(fk.ReferencedTableIndex))
   107  	for _, t := range fk.ReferencedTableColumns {
   108  		_ = binary.Write(&bb, binary.LittleEndian, t)
   109  	}
   110  	bb.Write([]byte{byte(fk.OnUpdate), byte(fk.OnDelete)})
   111  
   112  	return hash.Of(bb.Bytes())
   113  }
   114  
   115  // IsSelfReferential returns whether the table declaring the foreign key is also referenced by the foreign key.
   116  func (fk ForeignKey) IsSelfReferential() bool {
   117  	return strings.ToLower(fk.TableName) == strings.ToLower(fk.ReferencedTableName)
   118  }
   119  
   120  // ValidateReferencedTableSchema verifies that the given schema matches the expectation of the referenced table.
   121  func (fk ForeignKey) ValidateReferencedTableSchema(sch schema.Schema) error {
   122  	allSchCols := sch.GetAllCols()
   123  	for _, colTag := range fk.ReferencedTableColumns {
   124  		_, ok := allSchCols.GetByTag(colTag)
   125  		if !ok {
   126  			return fmt.Errorf("foreign key `%s` has entered an invalid state, referenced table `%s` has unexpected schema",
   127  				fk.Name, fk.ReferencedTableName)
   128  		}
   129  	}
   130  	if !sch.Indexes().Contains(fk.ReferencedTableIndex) {
   131  		return fmt.Errorf("foreign key `%s` has entered an invalid state, referenced table `%s` is missing the index `%s`",
   132  			fk.Name, fk.ReferencedTableName, fk.ReferencedTableIndex)
   133  	}
   134  	return nil
   135  }
   136  
   137  // ValidateTableSchema verifies that the given schema matches the expectation of the declaring table.
   138  func (fk ForeignKey) ValidateTableSchema(sch schema.Schema) error {
   139  	allSchCols := sch.GetAllCols()
   140  	for _, colTag := range fk.TableColumns {
   141  		_, ok := allSchCols.GetByTag(colTag)
   142  		if !ok {
   143  			return fmt.Errorf("foreign key `%s` has entered an invalid state, table `%s` has unexpected schema", fk.Name, fk.TableName)
   144  		}
   145  	}
   146  	if !sch.Indexes().Contains(fk.TableIndex) {
   147  		return fmt.Errorf("foreign key `%s` has entered an invalid state, table `%s` is missing the index `%s`",
   148  			fk.Name, fk.TableName, fk.TableIndex)
   149  	}
   150  	return nil
   151  }
   152  
   153  // LoadForeignKeyCollection returns a new ForeignKeyCollection using the provided map returned previously by GetMap.
   154  func LoadForeignKeyCollection(ctx context.Context, fkMap types.Map) (*ForeignKeyCollection, error) {
   155  	fkc := &ForeignKeyCollection{
   156  		foreignKeys: make(map[string]ForeignKey),
   157  	}
   158  	err := fkMap.IterAll(ctx, func(key, value types.Value) error {
   159  		foreignKey := &ForeignKey{}
   160  		err := marshal.Unmarshal(ctx, fkMap.Format(), value, foreignKey)
   161  		if err != nil {
   162  			return err
   163  		}
   164  		fkc.foreignKeys[string(key.(types.String))] = *foreignKey
   165  		return nil
   166  	})
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	return fkc, nil
   171  }
   172  
   173  func NewForeignKeyCollection(keys ...ForeignKey) (*ForeignKeyCollection, error) {
   174  	fkc := &ForeignKeyCollection{
   175  		foreignKeys: make(map[string]ForeignKey),
   176  	}
   177  	for _, k := range keys {
   178  		err := fkc.AddKeys(k)
   179  		if err != nil {
   180  			return nil, err
   181  		}
   182  	}
   183  	return fkc, nil
   184  }
   185  
   186  // AddKeys adds the given foreign key to the collection. Checks that the given name is unique in the collection, and that
   187  // both column counts are equal. All other validation should occur before being added to the collection.
   188  func (fkc *ForeignKeyCollection) AddKeys(fks ...ForeignKey) error {
   189  	for _, key := range fks {
   190  		if key.Name == "" {
   191  			// assign a name based on the hash
   192  			// 8 char = 5 base32 bytes, should be collision resistant
   193  			// TODO: constraint names should be unique, and this isn't guaranteed to be.
   194  			//  This logic needs to live at the table / DB level.
   195  			key.Name = key.HashOf().String()[:8]
   196  		}
   197  
   198  		if _, ok := fkc.GetByTags(key.TableColumns, key.ReferencedTableColumns); ok {
   199  			// this differs from MySQL's logic
   200  			return fmt.Errorf("a foreign key over columns %v and referenced columns %v already exists",
   201  				key.TableColumns, key.ReferencedTableColumns)
   202  		}
   203  		if _, ok := fkc.GetByNameCaseInsensitive(key.Name); ok {
   204  			return fmt.Errorf("a foreign key with the name `%s` already exists", key.Name)
   205  		}
   206  		if len(key.TableColumns) != len(key.ReferencedTableColumns) {
   207  			return fmt.Errorf("foreign keys must have the same number of columns declared and referenced")
   208  		}
   209  
   210  		fkc.foreignKeys[key.HashOf().String()] = key
   211  	}
   212  	return nil
   213  }
   214  
   215  // AllKeys returns a slice, sorted by name ascending, containing all of the foreign keys in this collection.
   216  func (fkc *ForeignKeyCollection) AllKeys() []ForeignKey {
   217  	fks := make([]ForeignKey, len(fkc.foreignKeys))
   218  	i := 0
   219  	for _, fk := range fkc.foreignKeys {
   220  		fks[i] = fk
   221  		i++
   222  	}
   223  	sort.Slice(fks, func(i, j int) bool {
   224  		return fks[i].Name < fks[j].Name
   225  	})
   226  	return fks
   227  }
   228  
   229  // Contains returns whether the given foreign key name already exists for this collection.
   230  func (fkc *ForeignKeyCollection) Contains(foreignKeyName string) bool {
   231  	_, ok := fkc.GetByNameCaseInsensitive(foreignKeyName)
   232  	return ok
   233  }
   234  
   235  // Count returns the number of indexes in this collection.
   236  func (fkc *ForeignKeyCollection) Count() int {
   237  	return len(fkc.foreignKeys)
   238  }
   239  
   240  // GetByNameCaseInsensitive returns a ForeignKey with a matching case-insensitive name, and whether a match exists.
   241  func (fkc *ForeignKeyCollection) GetByNameCaseInsensitive(foreignKeyName string) (ForeignKey, bool) {
   242  	if foreignKeyName == "" {
   243  		return ForeignKey{}, false
   244  	}
   245  	for _, fk := range fkc.foreignKeys {
   246  		if strings.ToLower(fk.Name) == strings.ToLower(foreignKeyName) {
   247  			return fk, true
   248  		}
   249  	}
   250  	return ForeignKey{}, false
   251  }
   252  
   253  // GetByTags gets the Foreign Key defined over the parent and child columns corresponding to tags parameters.
   254  func (fkc *ForeignKeyCollection) GetByTags(childTags, parentTags []uint64) (match ForeignKey, ok bool) {
   255  	_ = fkc.Iter(func(fk ForeignKey) (stop bool, err error) {
   256  		if len(fk.ReferencedTableColumns) != len(parentTags) {
   257  			return false, nil
   258  		}
   259  		for i, t := range fk.ReferencedTableColumns {
   260  			if t != parentTags[i] {
   261  				return false, nil
   262  			}
   263  		}
   264  
   265  		if len(fk.TableColumns) != len(childTags) {
   266  			return false, nil
   267  		}
   268  		for i, t := range fk.TableColumns {
   269  			if t != childTags[i] {
   270  				return false, nil
   271  			}
   272  		}
   273  		match, ok = fk, true
   274  		return true, nil
   275  	})
   276  	return match, ok
   277  }
   278  
   279  func (fkc *ForeignKeyCollection) Iter(cb func(fk ForeignKey) (stop bool, err error)) error {
   280  	for _, fk := range fkc.foreignKeys {
   281  		stop, err := cb(fk)
   282  		if err != nil {
   283  			return err
   284  		}
   285  		if stop {
   286  			return err
   287  		}
   288  	}
   289  	return nil
   290  }
   291  
   292  // KeysForTable returns all foreign keys that reference the given table in some capacity. The returned array
   293  // declaredFk contains all foreign keys in which this table declared the foreign key. The array referencedByFk contains
   294  // all foreign keys in which this table is the referenced table. If the table contains a self-referential foreign key,
   295  // it will be present in both declaresFk and referencedByFk. Each array is sorted by name ascending.
   296  func (fkc *ForeignKeyCollection) KeysForTable(tableName string) (declaredFk, referencedByFk []ForeignKey) {
   297  	lowercaseTblName := strings.ToLower(tableName)
   298  	for _, foreignKey := range fkc.foreignKeys {
   299  		if strings.ToLower(foreignKey.TableName) == lowercaseTblName {
   300  			declaredFk = append(declaredFk, foreignKey)
   301  		}
   302  		if strings.ToLower(foreignKey.ReferencedTableName) == lowercaseTblName {
   303  			referencedByFk = append(referencedByFk, foreignKey)
   304  		}
   305  	}
   306  	sort.Slice(declaredFk, func(i, j int) bool {
   307  		return declaredFk[i].Name < declaredFk[j].Name
   308  	})
   309  	sort.Slice(referencedByFk, func(i, j int) bool {
   310  		return referencedByFk[i].Name < referencedByFk[j].Name
   311  	})
   312  	return
   313  }
   314  
   315  // Map returns the collection as a Noms Map for persistence.
   316  func (fkc *ForeignKeyCollection) Map(ctx context.Context, vrw types.ValueReadWriter) (types.Map, error) {
   317  	fkMap, err := types.NewMap(ctx, vrw)
   318  	if err != nil {
   319  		return types.EmptyMap, err
   320  	}
   321  	fkMapEditor := fkMap.Edit()
   322  	for hashOf, foreignKey := range fkc.foreignKeys {
   323  		val, err := marshal.Marshal(ctx, vrw, foreignKey)
   324  		if err != nil {
   325  			return types.EmptyMap, err
   326  		}
   327  		fkMapEditor.Set(types.String(hashOf), val)
   328  	}
   329  	return fkMapEditor.Map(ctx)
   330  }
   331  
   332  // RemoveKeys removes any Foreign Keys with matching column set from the collection.
   333  func (fkc *ForeignKeyCollection) RemoveKeys(fks ...ForeignKey) {
   334  	drops := set.NewStrSet(nil)
   335  	for _, outgoing := range fks {
   336  		for k, existing := range fkc.foreignKeys {
   337  			if outgoing.EqualDefs(existing) {
   338  				drops.Add(k)
   339  			}
   340  		}
   341  	}
   342  	for _, k := range drops.AsSlice() {
   343  		delete(fkc.foreignKeys, k)
   344  	}
   345  }
   346  
   347  // RemoveKeyByName removes a foreign key from the collection. It does not remove the associated indexes from their
   348  // respective tables.
   349  func (fkc *ForeignKeyCollection) RemoveKeyByName(foreignKeyName string) error {
   350  	var key string
   351  	for k, fk := range fkc.foreignKeys {
   352  		if strings.ToLower(fk.Name) == strings.ToLower(foreignKeyName) {
   353  			key = k
   354  			break
   355  		}
   356  	}
   357  	if key == "" {
   358  		return fmt.Errorf("`%s` does not exist as a foreign key", foreignKeyName)
   359  	}
   360  	delete(fkc.foreignKeys, key)
   361  	return nil
   362  }
   363  
   364  // RemoveTables removes all foreign keys associated with the given tables, if permitted. The operation assumes that ALL
   365  // tables to be removed are in a single call, as splitting tables into different calls may result in unintended errors.
   366  func (fkc *ForeignKeyCollection) RemoveTables(ctx context.Context, tables ...string) error {
   367  	outgoing := set.NewStrSet(tables)
   368  	for _, fk := range fkc.foreignKeys {
   369  		dropChild := outgoing.Contains(fk.TableName)
   370  		dropParent := outgoing.Contains(fk.ReferencedTableName)
   371  		if dropParent && !dropChild {
   372  			return fmt.Errorf("unable to remove `%s` since it is referenced from table `%s`", fk.ReferencedTableName, fk.TableName)
   373  		}
   374  		if dropChild {
   375  			delete(fkc.foreignKeys, fk.HashOf().String())
   376  		}
   377  	}
   378  	return nil
   379  }
   380  
   381  // RenameTable updates all foreign key entries in the collection with the updated table name. Does not check for name
   382  // collisions.
   383  func (fkc *ForeignKeyCollection) RenameTable(oldTableName, newTableName string) {
   384  	updated := make(map[string]ForeignKey, len(fkc.foreignKeys))
   385  	for _, fk := range fkc.foreignKeys {
   386  		if fk.TableName == oldTableName {
   387  			fk.TableName = newTableName
   388  		}
   389  		if fk.ReferencedTableName == oldTableName {
   390  			fk.ReferencedTableName = newTableName
   391  		}
   392  		updated[fk.HashOf().String()] = fk
   393  	}
   394  	fkc.foreignKeys = updated
   395  }
   396  
   397  // Stage takes the keys to add and remove and updates the current collection. Does not perform any key validation nor
   398  // name uniqueness verification, as this is intended for use in commit staging. Adding a foreign key and updating (such
   399  // as a table rename) an existing one are functionally the same.
   400  func (fkc *ForeignKeyCollection) Stage(ctx context.Context, fksToAdd []ForeignKey, fksToRemove []ForeignKey) {
   401  	for _, fk := range fksToAdd {
   402  		fkc.foreignKeys[fk.HashOf().String()] = fk
   403  	}
   404  	for _, fk := range fksToRemove {
   405  		delete(fkc.foreignKeys, fk.HashOf().String())
   406  	}
   407  }
   408  
   409  // String returns the SQL reference option in uppercase.
   410  func (refOp ForeignKeyReferenceOption) String() string {
   411  	switch refOp {
   412  	case ForeignKeyReferenceOption_DefaultAction:
   413  		return "NONE SPECIFIED"
   414  	case ForeignKeyReferenceOption_Cascade:
   415  		return "CASCADE"
   416  	case ForeignKeyReferenceOption_NoAction:
   417  		return "NO ACTION"
   418  	case ForeignKeyReferenceOption_Restrict:
   419  		return "RESTRICT"
   420  	case ForeignKeyReferenceOption_SetNull:
   421  		return "SET NULL"
   422  	default:
   423  		return "INVALID"
   424  	}
   425  }
   426  
   427  // copy returns an exact copy of the calling collection. As collections are meant to be modified in-place, this ensures
   428  // that the original collection is not affected by any operations applied to the copied collection.
   429  func (fkc *ForeignKeyCollection) copy() *ForeignKeyCollection {
   430  	copiedForeignKeys := make(map[string]ForeignKey)
   431  	for hashOf, key := range fkc.foreignKeys {
   432  		copiedForeignKeys[hashOf] = key
   433  	}
   434  	return &ForeignKeyCollection{copiedForeignKeys}
   435  }
   436  
   437  // ValidateData ensures that the foreign key is valid by comparing the index data from the given table
   438  // against the index data from the referenced table.
   439  func (fk ForeignKey) ValidateData(ctx context.Context, childIdx, parentIdx types.Map, childDef, parentDef schema.Index) error {
   440  	if fk.ReferencedTableIndex != parentDef.Name() {
   441  		return fmt.Errorf("cannot validate data as wrong referenced index was given: expected `%s` but received `%s`",
   442  			fk.ReferencedTableIndex, parentDef.Name())
   443  	}
   444  
   445  	tagMap := make(map[uint64]uint64, len(fk.TableColumns))
   446  	for i, childTag := range fk.TableColumns {
   447  		tagMap[childTag] = fk.ReferencedTableColumns[i]
   448  	}
   449  
   450  	// FieldMappings ignore columns not in the tagMap
   451  	fm, err := rowconv.NewFieldMapping(childDef.Schema(), parentDef.Schema(), tagMap)
   452  	if err != nil {
   453  		return err
   454  	}
   455  
   456  	vrw := types.NewMemoryValueStore() // We are checking fks rather than persisting any values, so an internal VRW can be used
   457  	rc, err := rowconv.NewRowConverter(ctx, vrw, fm)
   458  	if err != nil {
   459  		return err
   460  	}
   461  
   462  	rdr, err := noms.NewNomsMapReader(ctx, childIdx, childDef.Schema())
   463  	if err != nil {
   464  		return err
   465  	}
   466  
   467  	for {
   468  		childIdxRow, err := rdr.ReadRow(ctx)
   469  		if err == io.EOF {
   470  			break
   471  		}
   472  		if err != nil {
   473  			return err
   474  		}
   475  
   476  		// Check if there are any NULL values, as they should be skipped
   477  		hasNulls := false
   478  		_, err = childIdxRow.IterSchema(childDef.Schema(), func(tag uint64, val types.Value) (stop bool, err error) {
   479  			if types.IsNull(val) {
   480  				hasNulls = true
   481  				return true, nil
   482  			}
   483  			return false, nil
   484  		})
   485  		if err != nil {
   486  			return err
   487  		}
   488  		if hasNulls {
   489  			continue
   490  		}
   491  
   492  		parentIdxRow, err := rc.Convert(childIdxRow)
   493  		if err != nil {
   494  			return err
   495  		}
   496  		if row.IsEmpty(parentIdxRow) {
   497  			continue
   498  		}
   499  
   500  		partial, err := row.ReduceToIndexPartialKey(parentDef, parentIdxRow)
   501  		if err != nil {
   502  			return err
   503  		}
   504  
   505  		indexIter := noms.NewNomsRangeReader(parentDef.Schema(), parentIdx,
   506  			[]*noms.ReadRange{{Start: partial, Inclusive: true, Reverse: false, Check: func(tuple types.Tuple) (bool, error) {
   507  				return tuple.StartsWith(partial), nil
   508  			}}},
   509  		)
   510  
   511  		switch _, err = indexIter.ReadRow(ctx); err {
   512  		case nil:
   513  			continue // parent table contains child key
   514  		case io.EOF:
   515  			indexKeyStr, _ := types.EncodedValue(ctx, partial)
   516  			return fmt.Errorf("foreign key violation on `%s`.`%s`: `%s`", fk.Name, fk.TableName, indexKeyStr)
   517  		default:
   518  			return err
   519  		}
   520  	}
   521  
   522  	return nil
   523  }