github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/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  	"errors"
    22  	"fmt"
    23  	"sort"
    24  	"strings"
    25  
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/row"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    28  	"github.com/dolthub/dolt/go/libraries/utils/set"
    29  	"github.com/dolthub/dolt/go/store/hash"
    30  	"github.com/dolthub/dolt/go/store/types"
    31  
    32  	"github.com/dolthub/go-mysql-server/sql"
    33  )
    34  
    35  // ForeignKeyCollection represents the collection of foreign keys for a root value.
    36  type ForeignKeyCollection struct {
    37  	foreignKeys map[string]ForeignKey
    38  }
    39  
    40  // ForeignKeyViolationError represents a set of foreign key violations for a table.
    41  type ForeignKeyViolationError struct {
    42  	ForeignKey    ForeignKey
    43  	Schema        schema.Schema
    44  	ViolationRows []row.Row
    45  }
    46  
    47  // Error implements the interface error.
    48  func (f *ForeignKeyViolationError) Error() string {
    49  	if len(f.ViolationRows) == 0 {
    50  		return "no violations were found, should not be an error"
    51  	}
    52  	sb := strings.Builder{}
    53  	const earlyTerminationLimit = 50
    54  	terminatedEarly := false
    55  	for i := range f.ViolationRows {
    56  		if i >= earlyTerminationLimit {
    57  			terminatedEarly = true
    58  			break
    59  		}
    60  		key, _ := f.ViolationRows[i].NomsMapKey(f.Schema).Value(context.Background())
    61  		val, _ := f.ViolationRows[i].NomsMapValue(f.Schema).Value(context.Background())
    62  		valSlice, _ := val.(types.Tuple).AsSlice()
    63  		all, _ := key.(types.Tuple).Append(valSlice...)
    64  		str, _ := types.EncodedValue(context.Background(), all)
    65  		sb.WriteRune('\n')
    66  		sb.WriteString(str)
    67  	}
    68  	if terminatedEarly {
    69  		return fmt.Sprintf("foreign key violations on `%s`.`%s`:%s\n%d more violations are not being displayed",
    70  			f.ForeignKey.Name, f.ForeignKey.TableName, sb.String(), len(f.ViolationRows)-earlyTerminationLimit)
    71  	} else {
    72  		return fmt.Sprintf("foreign key violations on `%s`.`%s`:%s", f.ForeignKey.Name, f.ForeignKey.TableName, sb.String())
    73  	}
    74  }
    75  
    76  var _ error = (*ForeignKeyViolationError)(nil)
    77  
    78  type ForeignKeyReferentialAction byte
    79  
    80  const (
    81  	ForeignKeyReferentialAction_DefaultAction ForeignKeyReferentialAction = iota
    82  	ForeignKeyReferentialAction_Cascade
    83  	ForeignKeyReferentialAction_NoAction
    84  	ForeignKeyReferentialAction_Restrict
    85  	ForeignKeyReferentialAction_SetNull
    86  )
    87  
    88  // ForeignKey is the complete, internal representation of a Foreign Key.
    89  type ForeignKey struct {
    90  	Name                   string                      `noms:"name" json:"name"`
    91  	TableName              string                      `noms:"tbl_name" json:"tbl_name"`
    92  	TableIndex             string                      `noms:"tbl_index" json:"tbl_index"`
    93  	TableColumns           []uint64                    `noms:"tbl_cols" json:"tbl_cols"`
    94  	ReferencedTableName    string                      `noms:"ref_tbl_name" json:"ref_tbl_name"`
    95  	ReferencedTableIndex   string                      `noms:"ref_tbl_index" json:"ref_tbl_index"`
    96  	ReferencedTableColumns []uint64                    `noms:"ref_tbl_cols" json:"ref_tbl_cols"`
    97  	OnUpdate               ForeignKeyReferentialAction `noms:"on_update" json:"on_update"`
    98  	OnDelete               ForeignKeyReferentialAction `noms:"on_delete" json:"on_delete"`
    99  	UnresolvedFKDetails    UnresolvedFKDetails         `noms:"unres_fk,omitempty" json:"unres_fk,omitempty"`
   100  }
   101  
   102  // UnresolvedFKDetails contains any details necessary for an unresolved foreign key to resolve to a valid foreign key.
   103  type UnresolvedFKDetails struct {
   104  	TableColumns           []string `noms:"x_tbl_cols" json:"x_tbl_cols"`
   105  	ReferencedTableColumns []string `noms:"x_ref_tbl_cols" json:"x_ref_tbl_cols"`
   106  }
   107  
   108  // EqualDefs returns whether two foreign keys have the same definition over the same column sets.
   109  // It does not compare table names or foreign key names.
   110  func (fk ForeignKey) EqualDefs(other ForeignKey) bool {
   111  	if len(fk.TableColumns) != len(other.TableColumns) || len(fk.ReferencedTableColumns) != len(other.ReferencedTableColumns) {
   112  		return false
   113  	}
   114  	for i := range fk.TableColumns {
   115  		if fk.TableColumns[i] != other.TableColumns[i] {
   116  			return false
   117  		}
   118  	}
   119  	for i := range fk.ReferencedTableColumns {
   120  		if fk.ReferencedTableColumns[i] != other.ReferencedTableColumns[i] {
   121  			return false
   122  		}
   123  	}
   124  	return fk.Name == other.Name &&
   125  		fk.OnUpdate == other.OnUpdate &&
   126  		fk.OnDelete == other.OnDelete
   127  }
   128  
   129  // Equals compares this ForeignKey to |other| and returns true if they are equal. Foreign keys can either be in
   130  // a "resolved" state, where the referenced columns in the parent and child tables are identified by column tags,
   131  // or in an "unresolved" state where the reference columns in the parent and child are still identified by strings.
   132  // If one foreign key is resolved and one is unresolved, the logic for comparing them requires resolving the string
   133  // column names to column tags, which is why |fkSchemasByName| and |otherSchemasByName| are passed in. Each of these
   134  // is a map of table schemas for |fk| and |other|, where the child table and every parent table referenced in the
   135  // foreign key is present in the map.
   136  func (fk ForeignKey) Equals(other ForeignKey, fkSchemasByName, otherSchemasByName map[string]schema.Schema) bool {
   137  	// If both FKs are resolved or unresolved, we can just deeply compare them
   138  	if fk.IsResolved() == other.IsResolved() {
   139  		return fk.DeepEquals(other)
   140  	}
   141  
   142  	// Otherwise, one FK is resolved and one is not, so we need to work a little harder
   143  	// to calculate equality since their referenced columns are represented differently.
   144  	// First check the attributes that don't change when an FK is resolved or unresolved.
   145  	if fk.Name != other.Name &&
   146  		fk.TableName != other.TableName &&
   147  		fk.ReferencedTableName != other.ReferencedTableName &&
   148  		fk.TableIndex != other.TableIndex &&
   149  		fk.ReferencedTableIndex != other.ReferencedTableIndex &&
   150  		fk.OnUpdate == other.OnUpdate &&
   151  		fk.OnDelete == other.OnDelete {
   152  		return false
   153  	}
   154  
   155  	// Sort out which FK is resolved and which is not
   156  	var resolvedFK, unresolvedFK ForeignKey
   157  	var resolvedSchemasByName map[string]schema.Schema
   158  	if fk.IsResolved() {
   159  		resolvedFK, unresolvedFK, resolvedSchemasByName = fk, other, fkSchemasByName
   160  	} else {
   161  		resolvedFK, unresolvedFK, resolvedSchemasByName = other, fk, otherSchemasByName
   162  	}
   163  
   164  	// Check the columns on the child table
   165  	if len(resolvedFK.TableColumns) != len(unresolvedFK.UnresolvedFKDetails.TableColumns) {
   166  		return false
   167  	}
   168  	for i, tag := range resolvedFK.TableColumns {
   169  		unresolvedColName := unresolvedFK.UnresolvedFKDetails.TableColumns[i]
   170  		resolvedSch, ok := resolvedSchemasByName[resolvedFK.TableName]
   171  		if !ok {
   172  			return false
   173  		}
   174  		resolvedCol, ok := resolvedSch.GetAllCols().GetByTag(tag)
   175  		if !ok {
   176  			return false
   177  		}
   178  		if resolvedCol.Name != unresolvedColName {
   179  			return false
   180  		}
   181  	}
   182  
   183  	// Check the columns on the parent table
   184  	if len(resolvedFK.ReferencedTableColumns) != len(unresolvedFK.UnresolvedFKDetails.ReferencedTableColumns) {
   185  		return false
   186  	}
   187  	for i, tag := range resolvedFK.ReferencedTableColumns {
   188  		unresolvedColName := unresolvedFK.UnresolvedFKDetails.ReferencedTableColumns[i]
   189  		resolvedSch, ok := resolvedSchemasByName[unresolvedFK.ReferencedTableName]
   190  		if !ok {
   191  			return false
   192  		}
   193  		resolvedCol, ok := resolvedSch.GetAllCols().GetByTag(tag)
   194  		if !ok {
   195  			return false
   196  		}
   197  		if resolvedCol.Name != unresolvedColName {
   198  			return false
   199  		}
   200  	}
   201  
   202  	return true
   203  }
   204  
   205  // DeepEquals compares all attributes of a foreign key to another, including name and
   206  // table names. Note that if one foreign key is resolved and the other is NOT resolved,
   207  // then this function will not calculate equality correctly. When comparing a resolved
   208  // FK with an unresolved FK, the ForeignKey.Equals() function should be used instead.
   209  func (fk ForeignKey) DeepEquals(other ForeignKey) bool {
   210  	if !fk.EqualDefs(other) {
   211  		return false
   212  	}
   213  	return fk.Name == other.Name &&
   214  		fk.TableName == other.TableName &&
   215  		fk.ReferencedTableName == other.ReferencedTableName &&
   216  		fk.TableIndex == other.TableIndex &&
   217  		fk.ReferencedTableIndex == other.ReferencedTableIndex
   218  }
   219  
   220  // HashOf returns the Noms hash of a ForeignKey.
   221  func (fk ForeignKey) HashOf() (hash.Hash, error) {
   222  	var fields []interface{}
   223  	fields = append(fields, fk.Name, fk.TableName, fk.TableIndex)
   224  	for _, t := range fk.TableColumns {
   225  		fields = append(fields, t)
   226  	}
   227  	fields = append(fields, fk.ReferencedTableName, fk.ReferencedTableIndex)
   228  	for _, t := range fk.ReferencedTableColumns {
   229  		fields = append(fields, t)
   230  	}
   231  	fields = append(fields, []byte{byte(fk.OnUpdate), byte(fk.OnDelete)})
   232  	for _, col := range fk.UnresolvedFKDetails.TableColumns {
   233  		fields = append(fields, col)
   234  	}
   235  	for _, col := range fk.UnresolvedFKDetails.ReferencedTableColumns {
   236  		fields = append(fields, col)
   237  	}
   238  
   239  	var bb bytes.Buffer
   240  	for _, field := range fields {
   241  		var err error
   242  		switch t := field.(type) {
   243  		case string:
   244  			_, err = bb.Write([]byte(t))
   245  		case []byte:
   246  			_, err = bb.Write(t)
   247  		case uint64:
   248  			err = binary.Write(&bb, binary.LittleEndian, t)
   249  		default:
   250  			return hash.Hash{}, fmt.Errorf("unsupported type %T", t)
   251  		}
   252  		if err != nil {
   253  			return hash.Hash{}, err
   254  		}
   255  	}
   256  
   257  	return hash.Of(bb.Bytes()), nil
   258  }
   259  
   260  // CombinedHash returns a combined hash value for all foreign keys in the slice provided.
   261  // An empty slice has a zero hash.
   262  func CombinedHash(fks []ForeignKey) (hash.Hash, error) {
   263  	if len(fks) == 0 {
   264  		return hash.Hash{}, nil
   265  	}
   266  
   267  	var bb bytes.Buffer
   268  	for _, fk := range fks {
   269  		h, err := fk.HashOf()
   270  		if err != nil {
   271  			return hash.Hash{}, err
   272  		}
   273  		bb.Write(h[:])
   274  	}
   275  
   276  	return hash.Of(bb.Bytes()), nil
   277  }
   278  
   279  // IsSelfReferential returns whether the table declaring the foreign key is also referenced by the foreign key.
   280  func (fk ForeignKey) IsSelfReferential() bool {
   281  	return strings.ToLower(fk.TableName) == strings.ToLower(fk.ReferencedTableName)
   282  }
   283  
   284  // IsResolved returns whether the foreign key has been resolved.
   285  func (fk ForeignKey) IsResolved() bool {
   286  	return len(fk.TableColumns) > 0 && len(fk.ReferencedTableColumns) > 0
   287  }
   288  
   289  // ValidateReferencedTableSchema verifies that the given schema matches the expectation of the referenced table.
   290  func (fk ForeignKey) ValidateReferencedTableSchema(sch schema.Schema) error {
   291  	// An unresolved foreign key will be validated later, so we don't return an error here.
   292  	if !fk.IsResolved() {
   293  		return nil
   294  	}
   295  	allSchCols := sch.GetAllCols()
   296  	for _, colTag := range fk.ReferencedTableColumns {
   297  		_, ok := allSchCols.GetByTag(colTag)
   298  		if !ok {
   299  			return fmt.Errorf("foreign key `%s` has entered an invalid state, referenced table `%s` has unexpected schema",
   300  				fk.Name, fk.ReferencedTableName)
   301  		}
   302  	}
   303  
   304  	if (fk.ReferencedTableIndex != "" && !sch.Indexes().Contains(fk.ReferencedTableIndex)) || (fk.ReferencedTableIndex == "" && sch.GetPKCols().Size() < len(fk.ReferencedTableColumns)) {
   305  		return fmt.Errorf("foreign key `%s` has entered an invalid state, referenced table `%s` is missing the index `%s`",
   306  			fk.Name, fk.ReferencedTableName, fk.ReferencedTableIndex)
   307  	}
   308  	return nil
   309  }
   310  
   311  // ValidateTableSchema verifies that the given schema matches the expectation of the declaring table.
   312  func (fk ForeignKey) ValidateTableSchema(sch schema.Schema) error {
   313  	// An unresolved foreign key will be validated later, so we don't return an error here.
   314  	if !fk.IsResolved() {
   315  		return nil
   316  	}
   317  	allSchCols := sch.GetAllCols()
   318  	for _, colTag := range fk.TableColumns {
   319  		_, ok := allSchCols.GetByTag(colTag)
   320  		if !ok {
   321  			return fmt.Errorf("foreign key `%s` has entered an invalid state, table `%s` has unexpected schema", fk.Name, fk.TableName)
   322  		}
   323  	}
   324  	if (fk.TableIndex != "" && !sch.Indexes().Contains(fk.TableIndex)) || (fk.TableIndex == "" && sch.GetPKCols().Size() < len(fk.TableColumns)) {
   325  		return fmt.Errorf("foreign key `%s` has entered an invalid state, table `%s` is missing the index `%s`",
   326  			fk.Name, fk.TableName, fk.TableIndex)
   327  	}
   328  	return nil
   329  }
   330  
   331  func NewForeignKeyCollection(keys ...ForeignKey) (*ForeignKeyCollection, error) {
   332  	fkc := &ForeignKeyCollection{
   333  		foreignKeys: make(map[string]ForeignKey),
   334  	}
   335  	for _, k := range keys {
   336  		err := fkc.AddKeys(k)
   337  		if err != nil {
   338  			return nil, err
   339  		}
   340  	}
   341  	return fkc, nil
   342  }
   343  
   344  // AddKeys adds the given foreign key to the collection. Checks that the given name is unique in the collection, and that
   345  // both column counts are equal. All other validation should occur before being added to the collection.
   346  func (fkc *ForeignKeyCollection) AddKeys(fks ...ForeignKey) error {
   347  	for _, key := range fks {
   348  		if _, ok := fkc.GetByNameCaseInsensitive(key.Name); ok {
   349  			return sql.ErrForeignKeyDuplicateName.New(key.Name)
   350  		}
   351  		if len(key.TableColumns) != len(key.ReferencedTableColumns) {
   352  			return fmt.Errorf("foreign keys must have the same number of columns declared and referenced")
   353  		}
   354  
   355  		fkHash, err := key.HashOf()
   356  		if err != nil {
   357  			return err
   358  		}
   359  		fkc.foreignKeys[fkHash.String()] = key
   360  	}
   361  	return nil
   362  }
   363  
   364  // AllKeys returns a slice, sorted by name ascending, containing all of the foreign keys in this collection.
   365  func (fkc *ForeignKeyCollection) AllKeys() []ForeignKey {
   366  	fks := make([]ForeignKey, len(fkc.foreignKeys))
   367  	i := 0
   368  	for _, fk := range fkc.foreignKeys {
   369  		fks[i] = fk
   370  		i++
   371  	}
   372  	sort.Slice(fks, func(i, j int) bool {
   373  		return fks[i].Name < fks[j].Name
   374  	})
   375  	return fks
   376  }
   377  
   378  // Contains returns whether the given foreign key name already exists for this collection.
   379  func (fkc *ForeignKeyCollection) Contains(foreignKeyName string) bool {
   380  	_, ok := fkc.GetByNameCaseInsensitive(foreignKeyName)
   381  	return ok
   382  }
   383  
   384  // HashOf returns the hash of the serialized form of this foreign key collection
   385  func (fkc *ForeignKeyCollection) HashOf(ctx context.Context, vrw types.ValueReadWriter) (hash.Hash, error) {
   386  	serialized, err := SerializeForeignKeys(ctx, vrw, fkc)
   387  	if err != nil {
   388  		return hash.Hash{}, err
   389  	}
   390  
   391  	return serialized.Hash(vrw.Format())
   392  }
   393  
   394  // Count returns the number of indexes in this collection.
   395  func (fkc *ForeignKeyCollection) Count() int {
   396  	return len(fkc.foreignKeys)
   397  }
   398  
   399  // GetByNameCaseInsensitive returns a ForeignKey with a matching case-insensitive name, and whether a match exists.
   400  func (fkc *ForeignKeyCollection) GetByNameCaseInsensitive(foreignKeyName string) (ForeignKey, bool) {
   401  	if foreignKeyName == "" {
   402  		return ForeignKey{}, false
   403  	}
   404  	for _, fk := range fkc.foreignKeys {
   405  		if strings.ToLower(fk.Name) == strings.ToLower(foreignKeyName) {
   406  			return fk, true
   407  		}
   408  	}
   409  	return ForeignKey{}, false
   410  }
   411  
   412  type FkIndexUpdate struct {
   413  	FkName  string
   414  	FromIdx string
   415  	ToIdx   string
   416  }
   417  
   418  // UpdateIndexes updates the indexes used by the foreign keys as outlined by the update structs given. All foreign
   419  // keys updated in this manner must belong to the same table, whose schema is provided.
   420  func (fkc *ForeignKeyCollection) UpdateIndexes(ctx context.Context, tableSchema schema.Schema, updates []FkIndexUpdate) error {
   421  	for _, u := range updates {
   422  		fk, ok := fkc.GetByNameCaseInsensitive(u.FkName)
   423  		if !ok {
   424  			return errors.New("foreign key not found")
   425  		}
   426  		fkc.RemoveKeys(fk)
   427  		fk.ReferencedTableIndex = u.ToIdx
   428  
   429  		err := fkc.AddKeys(fk)
   430  		if err != nil {
   431  			return err
   432  		}
   433  
   434  		err = fk.ValidateReferencedTableSchema(tableSchema)
   435  		if err != nil {
   436  			return err
   437  		}
   438  	}
   439  
   440  	return nil
   441  }
   442  
   443  // GetByTags gets the ForeignKey defined over the parent and child columns corresponding to their tags.
   444  func (fkc *ForeignKeyCollection) GetByTags(childTags, parentTags []uint64) (ForeignKey, bool) {
   445  	if len(childTags) == 0 || len(parentTags) == 0 {
   446  		return ForeignKey{}, false
   447  	}
   448  OuterLoop:
   449  	for _, fk := range fkc.foreignKeys {
   450  		if len(fk.ReferencedTableColumns) != len(parentTags) {
   451  			continue
   452  		}
   453  		for i, t := range fk.ReferencedTableColumns {
   454  			if t != parentTags[i] {
   455  				continue OuterLoop
   456  			}
   457  		}
   458  
   459  		if len(fk.TableColumns) != len(childTags) {
   460  			continue
   461  		}
   462  		for i, t := range fk.TableColumns {
   463  			if t != childTags[i] {
   464  				continue OuterLoop
   465  			}
   466  		}
   467  		return fk, true
   468  	}
   469  	return ForeignKey{}, false
   470  }
   471  
   472  // GetMatchingKey gets the ForeignKey defined over the parent and child columns. If the given foreign key is resolved,
   473  // then both resolved and unresolved keys are checked for a match. If the given foreign key is unresolved, then ONLY
   474  // unresolved keys may be found.
   475  //
   476  // This discrepancy is due to the primary uses for this function. It is assumed that the ForeignKeyCollection is an
   477  // ancestor collection compared to the collection that the given key comes from. Therefore, the key found in the
   478  // ancestor will usually be the unresolved version of the given key, hence the comparison is valid. However, if the
   479  // given key is unresolved, it is treated as a new key, which cannot be matched to a resolved key that previously
   480  // existed.
   481  //
   482  // The given schema map is keyed by table name, and is used in the event that the given key is resolved and any keys in
   483  // the collection are unresolved. A "dirty resolution" is performed, which matches the column names to tags, and then a
   484  // standard tag comparison is performed. If a table or column is not in the map, then the foreign key is ignored.
   485  func (fkc *ForeignKeyCollection) GetMatchingKey(fk ForeignKey, allSchemas map[string]schema.Schema) (ForeignKey, bool) {
   486  	if !fk.IsResolved() {
   487  		// The given foreign key is unresolved, so we only look for matches on unresolved keys
   488  	OuterLoopUnresolved:
   489  		for _, existingFk := range fkc.foreignKeys {
   490  			// For unresolved keys, the table name is important (column tags are globally unique, column names are not)
   491  			if existingFk.IsResolved() ||
   492  				fk.TableName != existingFk.TableName ||
   493  				fk.ReferencedTableName != existingFk.ReferencedTableName ||
   494  				len(fk.UnresolvedFKDetails.TableColumns) != len(existingFk.UnresolvedFKDetails.TableColumns) ||
   495  				len(fk.UnresolvedFKDetails.ReferencedTableColumns) != len(existingFk.UnresolvedFKDetails.ReferencedTableColumns) {
   496  				continue
   497  			}
   498  			for i, fkCol := range fk.UnresolvedFKDetails.TableColumns {
   499  				if fkCol != existingFk.UnresolvedFKDetails.TableColumns[i] {
   500  					continue OuterLoopUnresolved
   501  				}
   502  			}
   503  			for i, fkCol := range fk.UnresolvedFKDetails.ReferencedTableColumns {
   504  				if fkCol != existingFk.UnresolvedFKDetails.ReferencedTableColumns[i] {
   505  					continue OuterLoopUnresolved
   506  				}
   507  			}
   508  			return existingFk, true
   509  		}
   510  		return ForeignKey{}, false
   511  	}
   512  	// The given foreign key is resolved, so we may match both resolved and unresolved keys
   513  OuterLoopResolved:
   514  	for _, existingFk := range fkc.foreignKeys {
   515  		if existingFk.IsResolved() {
   516  			// When both are resolved, we do a standard tag comparison
   517  			if len(fk.TableColumns) != len(existingFk.TableColumns) ||
   518  				len(fk.ReferencedTableColumns) != len(existingFk.ReferencedTableColumns) {
   519  				continue
   520  			}
   521  			for i, tag := range fk.TableColumns {
   522  				if tag != existingFk.TableColumns[i] {
   523  					continue OuterLoopResolved
   524  				}
   525  			}
   526  			for i, tag := range fk.ReferencedTableColumns {
   527  				if tag != existingFk.ReferencedTableColumns[i] {
   528  					continue OuterLoopResolved
   529  				}
   530  			}
   531  			return existingFk, true
   532  		} else {
   533  			// Since the existing key is unresolved, we reference the schema map to get tags we can use
   534  			if len(fk.TableColumns) != len(existingFk.UnresolvedFKDetails.TableColumns) ||
   535  				len(fk.ReferencedTableColumns) != len(existingFk.UnresolvedFKDetails.ReferencedTableColumns) {
   536  				continue
   537  			}
   538  			tblSch, ok := allSchemas[existingFk.TableName]
   539  			if !ok {
   540  				continue
   541  			}
   542  			refTblSch, ok := allSchemas[existingFk.ReferencedTableName]
   543  			if !ok {
   544  				continue
   545  			}
   546  			for i, tag := range fk.TableColumns {
   547  				col, ok := tblSch.GetAllCols().GetByNameCaseInsensitive(existingFk.UnresolvedFKDetails.TableColumns[i])
   548  				if !ok || tag != col.Tag {
   549  					continue OuterLoopResolved
   550  				}
   551  			}
   552  			for i, tag := range fk.ReferencedTableColumns {
   553  				col, ok := refTblSch.GetAllCols().GetByNameCaseInsensitive(existingFk.UnresolvedFKDetails.ReferencedTableColumns[i])
   554  				if !ok || tag != col.Tag {
   555  					continue OuterLoopResolved
   556  				}
   557  			}
   558  			return existingFk, true
   559  		}
   560  	}
   561  	return ForeignKey{}, false
   562  }
   563  
   564  func (fkc *ForeignKeyCollection) Iter(cb func(fk ForeignKey) (stop bool, err error)) error {
   565  	for _, fk := range fkc.foreignKeys {
   566  		stop, err := cb(fk)
   567  		if err != nil {
   568  			return err
   569  		}
   570  		if stop {
   571  			return err
   572  		}
   573  	}
   574  	return nil
   575  }
   576  
   577  // KeysForTable returns all foreign keys that reference the given table in some capacity. The returned array
   578  // declaredFk contains all foreign keys in which this table declared the foreign key. The array referencedByFk contains
   579  // all foreign keys in which this table is the referenced table. If the table contains a self-referential foreign key,
   580  // it will be present in both declaresFk and referencedByFk. Each array is sorted by name ascending.
   581  func (fkc *ForeignKeyCollection) KeysForTable(tableName string) (declaredFk, referencedByFk []ForeignKey) {
   582  	lowercaseTblName := strings.ToLower(tableName)
   583  	for _, foreignKey := range fkc.foreignKeys {
   584  		if strings.ToLower(foreignKey.TableName) == lowercaseTblName {
   585  			declaredFk = append(declaredFk, foreignKey)
   586  		}
   587  		if strings.ToLower(foreignKey.ReferencedTableName) == lowercaseTblName {
   588  			referencedByFk = append(referencedByFk, foreignKey)
   589  		}
   590  	}
   591  	sort.Slice(declaredFk, func(i, j int) bool {
   592  		return declaredFk[i].Name < declaredFk[j].Name
   593  	})
   594  	sort.Slice(referencedByFk, func(i, j int) bool {
   595  		return referencedByFk[i].Name < referencedByFk[j].Name
   596  	})
   597  	return
   598  }
   599  
   600  // RemoveKeys removes any Foreign Keys with matching column set from the collection.
   601  func (fkc *ForeignKeyCollection) RemoveKeys(fks ...ForeignKey) {
   602  	drops := set.NewStrSet(nil)
   603  	for _, outgoing := range fks {
   604  		for k, existing := range fkc.foreignKeys {
   605  			if outgoing.EqualDefs(existing) {
   606  				drops.Add(k)
   607  			}
   608  		}
   609  	}
   610  	for _, k := range drops.AsSlice() {
   611  		delete(fkc.foreignKeys, k)
   612  	}
   613  }
   614  
   615  // RemoveKeyByName removes a foreign key from the collection. It does not remove the associated indexes from their
   616  // respective tables. Returns true if the key was successfully removed.
   617  func (fkc *ForeignKeyCollection) RemoveKeyByName(foreignKeyName string) bool {
   618  	var key string
   619  	for k, fk := range fkc.foreignKeys {
   620  		if strings.ToLower(fk.Name) == strings.ToLower(foreignKeyName) {
   621  			key = k
   622  			break
   623  		}
   624  	}
   625  	if key == "" {
   626  		return false
   627  	}
   628  	delete(fkc.foreignKeys, key)
   629  	return true
   630  }
   631  
   632  // RemoveTables removes all foreign keys associated with the given tables, if permitted. The operation assumes that ALL
   633  // tables to be removed are in a single call, as splitting tables into different calls may result in unintended errors.
   634  func (fkc *ForeignKeyCollection) RemoveTables(ctx context.Context, tables ...string) error {
   635  	outgoing := set.NewStrSet(tables)
   636  	for _, fk := range fkc.foreignKeys {
   637  		dropChild := outgoing.Contains(fk.TableName)
   638  		dropParent := outgoing.Contains(fk.ReferencedTableName)
   639  		if dropParent && !dropChild {
   640  			return fmt.Errorf("unable to remove `%s` since it is referenced from table `%s`", fk.ReferencedTableName, fk.TableName)
   641  		}
   642  		if dropChild {
   643  			fkHash, err := fk.HashOf()
   644  			if err != nil {
   645  				return err
   646  			}
   647  			delete(fkc.foreignKeys, fkHash.String())
   648  		}
   649  	}
   650  	return nil
   651  }
   652  
   653  // RemoveAndUnresolveTables removes all foreign keys associated with the given tables. If a parent is dropped without
   654  // its child, then the foreign key goes to an unresolved state. The operation assumes that ALL tables to be removed are
   655  // in a single call, as splitting tables into different calls may result in unintended errors.
   656  func (fkc *ForeignKeyCollection) RemoveAndUnresolveTables(ctx context.Context, root RootValue, tables ...string) error {
   657  	outgoing := set.NewStrSet(tables)
   658  	for _, fk := range fkc.foreignKeys {
   659  		dropChild := outgoing.Contains(fk.TableName)
   660  		dropParent := outgoing.Contains(fk.ReferencedTableName)
   661  		if dropParent && !dropChild {
   662  			if !fk.IsResolved() {
   663  				continue
   664  			}
   665  
   666  			fkHash, err := fk.HashOf()
   667  			if err != nil {
   668  				return err
   669  			}
   670  			delete(fkc.foreignKeys, fkHash.String())
   671  
   672  			fk.UnresolvedFKDetails.TableColumns = make([]string, len(fk.TableColumns))
   673  			fk.UnresolvedFKDetails.ReferencedTableColumns = make([]string, len(fk.ReferencedTableColumns))
   674  
   675  			tbl, ok, err := root.GetTable(ctx, TableName{Name: fk.TableName})
   676  			if err != nil {
   677  				return err
   678  			}
   679  			if !ok {
   680  				return fmt.Errorf("table `%s` declares the resolved foreign key `%s` but the table cannot be found",
   681  					fk.TableName, fk.Name)
   682  			}
   683  			sch, err := tbl.GetSchema(ctx)
   684  			if err != nil {
   685  				return err
   686  			}
   687  			for i, tag := range fk.TableColumns {
   688  				col, ok := sch.GetAllCols().GetByTag(tag)
   689  				if !ok {
   690  					return fmt.Errorf("table `%s` uses tag `%d` in foreign key `%s` but no matching column was found",
   691  						fk.TableName, tag, fk.Name)
   692  				}
   693  				fk.UnresolvedFKDetails.TableColumns[i] = col.Name
   694  			}
   695  
   696  			refTbl, ok, err := root.GetTable(ctx, TableName{Name: fk.ReferencedTableName})
   697  			if err != nil {
   698  				return err
   699  			}
   700  			if !ok {
   701  				return fmt.Errorf("table `%s` is referenced by the resolved foreign key `%s` but cannot be found",
   702  					fk.ReferencedTableName, fk.Name)
   703  			}
   704  			refSch, err := refTbl.GetSchema(ctx)
   705  			if err != nil {
   706  				return err
   707  			}
   708  			for i, tag := range fk.ReferencedTableColumns {
   709  				col, ok := refSch.GetAllCols().GetByTag(tag)
   710  				if !ok {
   711  					return fmt.Errorf("table `%s` uses tag `%d` in foreign key `%s` but no matching column was found",
   712  						fk.ReferencedTableName, tag, fk.Name)
   713  				}
   714  				fk.UnresolvedFKDetails.ReferencedTableColumns[i] = col.Name
   715  			}
   716  
   717  			fk.TableColumns = nil
   718  			fk.ReferencedTableColumns = nil
   719  			fk.TableIndex = ""
   720  			fk.ReferencedTableIndex = ""
   721  
   722  			fkHash, err = fk.HashOf()
   723  			if err != nil {
   724  				return err
   725  			}
   726  			fkc.foreignKeys[fkHash.String()] = fk
   727  		}
   728  		if dropChild {
   729  			fkHash, err := fk.HashOf()
   730  			if err != nil {
   731  				return err
   732  			}
   733  			delete(fkc.foreignKeys, fkHash.String())
   734  		}
   735  	}
   736  	return nil
   737  }
   738  
   739  // Tables returns the set of all tables that either declare a foreign key or are referenced by a foreign key.
   740  func (fkc *ForeignKeyCollection) Tables() map[string]struct{} {
   741  	tables := make(map[string]struct{})
   742  	for _, fk := range fkc.foreignKeys {
   743  		tables[fk.TableName] = struct{}{}
   744  		tables[fk.ReferencedTableName] = struct{}{}
   745  	}
   746  	return tables
   747  }
   748  
   749  // String returns the SQL reference option in uppercase.
   750  func (refOp ForeignKeyReferentialAction) String() string {
   751  	switch refOp {
   752  	case ForeignKeyReferentialAction_DefaultAction:
   753  		return "NONE SPECIFIED"
   754  	case ForeignKeyReferentialAction_Cascade:
   755  		return "CASCADE"
   756  	case ForeignKeyReferentialAction_NoAction:
   757  		return "NO ACTION"
   758  	case ForeignKeyReferentialAction_Restrict:
   759  		return "RESTRICT"
   760  	case ForeignKeyReferentialAction_SetNull:
   761  		return "SET NULL"
   762  	default:
   763  		return "INVALID"
   764  	}
   765  }
   766  
   767  // ReducedString returns the SQL reference option in uppercase. All reference options are functionally equivalent to
   768  // either RESTRICT, CASCADE, or SET NULL, therefore only one those three options are returned.
   769  func (refOp ForeignKeyReferentialAction) ReducedString() string {
   770  	switch refOp {
   771  	case ForeignKeyReferentialAction_DefaultAction, ForeignKeyReferentialAction_NoAction, ForeignKeyReferentialAction_Restrict:
   772  		return "RESTRICT"
   773  	case ForeignKeyReferentialAction_Cascade:
   774  		return "CASCADE"
   775  	case ForeignKeyReferentialAction_SetNull:
   776  		return "SET NULL"
   777  	default:
   778  		return "INVALID"
   779  	}
   780  }
   781  
   782  // ColumnHasFkRelationship returns a foreign key that uses this tag. Returns n
   783  func (fkc *ForeignKeyCollection) ColumnHasFkRelationship(tag uint64) (ForeignKey, bool) {
   784  	for _, key := range fkc.AllKeys() {
   785  		tags := append(key.TableColumns, key.ReferencedTableColumns...)
   786  
   787  		for _, keyTag := range tags {
   788  			if tag == keyTag {
   789  				return key, true
   790  			}
   791  		}
   792  	}
   793  
   794  	return ForeignKey{}, false
   795  }
   796  
   797  // Copy returns an exact copy of the calling collection. As collections are meant to be modified in-place, this ensures
   798  // that the original collection is not affected by any operations applied to the copied collection.
   799  func (fkc *ForeignKeyCollection) Copy() *ForeignKeyCollection {
   800  	copiedForeignKeys := make(map[string]ForeignKey)
   801  	for hashOf, key := range fkc.foreignKeys {
   802  		copiedForeignKeys[hashOf] = key
   803  	}
   804  	return &ForeignKeyCollection{copiedForeignKeys}
   805  }