github.com/dolthub/go-mysql-server@v0.18.0/sql/plan/foreign_key_editor.go (about)

     1  // Copyright 2022 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 plan
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"strings"
    21  
    22  	"github.com/dolthub/go-mysql-server/sql"
    23  )
    24  
    25  // ChildParentMapping is a mapping from the foreign key columns of a child schema to the parent schema. The position
    26  // in the slice corresponds to the position in the child schema, while the value at a given position refers to the
    27  // position in the parent schema. For all columns that are not in the foreign key definition, a value of -1 is returned.
    28  //
    29  // Here's an example:
    30  // parent Schema: x1, x2, x3, x4, x5
    31  // child Schema:  y1, y2, y3, y4
    32  // FOREIGN KEY (y2) REFERENCES parent (x4)
    33  //
    34  // The slice for the above would be [-1, 3, -1, -1]. The foreign key uses the column "y2" on the child, which is the
    35  // second position in the schema (and therefore the second position in the mapping). The equivalent parent column is
    36  // "x4", which is in the fourth position (so 3 with zero-based indexed).
    37  type ChildParentMapping []int
    38  
    39  // ForeignKeyRefActionData contains the mapper, editor, and child to parent mapping for processing referential actions.
    40  type ForeignKeyRefActionData struct {
    41  	RowMapper          *ForeignKeyRowMapper
    42  	Editor             *ForeignKeyEditor
    43  	ForeignKey         sql.ForeignKeyConstraint
    44  	ChildParentMapping ChildParentMapping
    45  }
    46  
    47  // ForeignKeyEditor handles update and delete operations, as they may have referential actions on other tables (such as
    48  // cascading). If this editor is Cyclical, then that means that following the referential actions will eventually lead
    49  // back to this same editor. Self-referential foreign keys are inherently cyclical.
    50  type ForeignKeyEditor struct {
    51  	Schema     sql.Schema
    52  	Editor     sql.ForeignKeyEditor
    53  	References []*ForeignKeyReferenceHandler
    54  	RefActions []ForeignKeyRefActionData
    55  	Cyclical   bool
    56  }
    57  
    58  // IsInitialized returns whether this editor has been initialized. The given map is used to prevent cycles, as editors
    59  // will reference themselves if a cycle is formed between foreign keys.
    60  func (fkEditor *ForeignKeyEditor) IsInitialized(editors map[*ForeignKeyEditor]struct{}) bool {
    61  	if fkEditor == nil || fkEditor.Editor == nil {
    62  		return false
    63  	}
    64  	if _, ok := editors[fkEditor]; ok {
    65  		return true
    66  	}
    67  	editors[fkEditor] = struct{}{}
    68  	for _, reference := range fkEditor.References {
    69  		if !reference.IsInitialized() {
    70  			return false
    71  		}
    72  	}
    73  	for _, refAction := range fkEditor.RefActions {
    74  		if !refAction.Editor.IsInitialized(editors) {
    75  			return false
    76  		}
    77  	}
    78  	return true
    79  }
    80  
    81  // Update handles both the standard UPDATE statement and propagated referential actions from a parent table's ON UPDATE.
    82  func (fkEditor *ForeignKeyEditor) Update(ctx *sql.Context, old sql.Row, new sql.Row, depth int) error {
    83  	for _, reference := range fkEditor.References {
    84  		if err := reference.CheckReference(ctx, new); err != nil {
    85  			return err
    86  		}
    87  	}
    88  	for _, refActionData := range fkEditor.RefActions {
    89  		switch refActionData.ForeignKey.OnUpdate {
    90  		default: // RESTRICT and friends
    91  			if err := fkEditor.OnUpdateRestrict(ctx, refActionData, old, new); err != nil {
    92  				return err
    93  			}
    94  		case sql.ForeignKeyReferentialAction_Cascade:
    95  		case sql.ForeignKeyReferentialAction_SetNull:
    96  		}
    97  	}
    98  	if err := fkEditor.Editor.Update(ctx, old, new); err != nil {
    99  		return err
   100  	}
   101  	for _, refActionData := range fkEditor.RefActions {
   102  		switch refActionData.ForeignKey.OnUpdate {
   103  		case sql.ForeignKeyReferentialAction_Cascade:
   104  			if err := fkEditor.OnUpdateCascade(ctx, refActionData, old, new, depth+1); err != nil {
   105  				return err
   106  			}
   107  		case sql.ForeignKeyReferentialAction_SetNull:
   108  			if err := fkEditor.OnUpdateSetNull(ctx, refActionData, old, new, depth+1); err != nil {
   109  				return err
   110  			}
   111  		}
   112  	}
   113  	return nil
   114  }
   115  
   116  // OnUpdateRestrict handles the ON UPDATE RESTRICT referential action.
   117  func (fkEditor *ForeignKeyEditor) OnUpdateRestrict(ctx *sql.Context, refActionData ForeignKeyRefActionData, old sql.Row, new sql.Row) error {
   118  	if ok, err := fkEditor.ColumnsUpdated(refActionData, old, new); err != nil {
   119  		return err
   120  	} else if !ok {
   121  		return nil
   122  	}
   123  
   124  	rowIter, err := refActionData.RowMapper.GetIter(ctx, old, false)
   125  	if err != nil {
   126  		return err
   127  	}
   128  	defer rowIter.Close(ctx)
   129  	if _, err = rowIter.Next(ctx); err == nil {
   130  		return sql.ErrForeignKeyParentViolation.New(refActionData.ForeignKey.Name,
   131  			refActionData.ForeignKey.Table, refActionData.ForeignKey.ParentTable, refActionData.RowMapper.GetKeyString(old))
   132  	}
   133  	if err != io.EOF {
   134  		return err
   135  	}
   136  	return nil
   137  }
   138  
   139  // OnUpdateCascade handles the ON UPDATE CASCADE referential action.
   140  func (fkEditor *ForeignKeyEditor) OnUpdateCascade(ctx *sql.Context, refActionData ForeignKeyRefActionData, old sql.Row, new sql.Row, depth int) error {
   141  	if ok, err := fkEditor.ColumnsUpdated(refActionData, old, new); err != nil {
   142  		return err
   143  	} else if !ok {
   144  		return nil
   145  	}
   146  
   147  	rowIter, err := refActionData.RowMapper.GetIter(ctx, old, false)
   148  	if err != nil {
   149  		return err
   150  	}
   151  	defer rowIter.Close(ctx)
   152  	var rowToUpdate sql.Row
   153  	for rowToUpdate, err = rowIter.Next(ctx); err == nil; rowToUpdate, err = rowIter.Next(ctx) {
   154  		if depth > 15 {
   155  			return sql.ErrForeignKeyDepthLimit.New()
   156  		}
   157  		updatedRow := make(sql.Row, len(rowToUpdate))
   158  		for i := range rowToUpdate {
   159  			mappedVal := refActionData.ChildParentMapping[i]
   160  			if mappedVal == -1 {
   161  				updatedRow[i] = rowToUpdate[i]
   162  			} else {
   163  				updatedRow[i] = new[mappedVal]
   164  			}
   165  		}
   166  		err = refActionData.Editor.Update(ctx, rowToUpdate, updatedRow, depth)
   167  		if err != nil {
   168  			return err
   169  		}
   170  	}
   171  	if err == io.EOF {
   172  		return nil
   173  	}
   174  	return err
   175  }
   176  
   177  // OnUpdateSetNull handles the ON UPDATE SET NULL referential action.
   178  func (fkEditor *ForeignKeyEditor) OnUpdateSetNull(ctx *sql.Context, refActionData ForeignKeyRefActionData, old sql.Row, new sql.Row, depth int) error {
   179  	if ok, err := fkEditor.ColumnsUpdated(refActionData, old, new); err != nil {
   180  		return err
   181  	} else if !ok {
   182  		return nil
   183  	}
   184  
   185  	rowIter, err := refActionData.RowMapper.GetIter(ctx, old, false)
   186  	if err != nil {
   187  		return err
   188  	}
   189  	defer rowIter.Close(ctx)
   190  	var rowToUpdate sql.Row
   191  	for rowToUpdate, err = rowIter.Next(ctx); err == nil; rowToUpdate, err = rowIter.Next(ctx) {
   192  		if depth > 15 {
   193  			return sql.ErrForeignKeyDepthLimit.New()
   194  		}
   195  		updatedRow := make(sql.Row, len(rowToUpdate))
   196  		for i := range rowToUpdate {
   197  			// Row contents are nil by default, so we only need to assign the non-affected values
   198  			if refActionData.ChildParentMapping[i] == -1 {
   199  				updatedRow[i] = rowToUpdate[i]
   200  			}
   201  		}
   202  		err = refActionData.Editor.Update(ctx, rowToUpdate, updatedRow, depth)
   203  		if err != nil {
   204  			return err
   205  		}
   206  	}
   207  	if err == io.EOF {
   208  		return nil
   209  	}
   210  	return err
   211  }
   212  
   213  // Delete handles both the standard DELETE statement and propagated referential actions from a parent table's ON DELETE.
   214  func (fkEditor *ForeignKeyEditor) Delete(ctx *sql.Context, row sql.Row, depth int) error {
   215  	//TODO: may need to process some cascades after the update to avoid recursive violations, write some tests on this
   216  	for _, refActionData := range fkEditor.RefActions {
   217  		switch refActionData.ForeignKey.OnDelete {
   218  		default: // RESTRICT and friends
   219  			if err := fkEditor.OnDeleteRestrict(ctx, refActionData, row); err != nil {
   220  				return err
   221  			}
   222  		case sql.ForeignKeyReferentialAction_Cascade:
   223  		case sql.ForeignKeyReferentialAction_SetNull:
   224  		}
   225  	}
   226  	if err := fkEditor.Editor.Delete(ctx, row); err != nil {
   227  		return err
   228  	}
   229  	for _, refActionData := range fkEditor.RefActions {
   230  		switch refActionData.ForeignKey.OnDelete {
   231  		case sql.ForeignKeyReferentialAction_Cascade:
   232  			if err := fkEditor.OnDeleteCascade(ctx, refActionData, row, depth+1); err != nil {
   233  				return err
   234  			}
   235  		case sql.ForeignKeyReferentialAction_SetNull:
   236  			if err := fkEditor.OnDeleteSetNull(ctx, refActionData, row, depth+1); err != nil {
   237  				return err
   238  			}
   239  		}
   240  	}
   241  	return nil
   242  }
   243  
   244  // OnDeleteRestrict handles the ON DELETE RESTRICT referential action.
   245  func (fkEditor *ForeignKeyEditor) OnDeleteRestrict(ctx *sql.Context, refActionData ForeignKeyRefActionData, row sql.Row) error {
   246  	rowIter, err := refActionData.RowMapper.GetIter(ctx, row, false)
   247  	if err != nil {
   248  		return err
   249  	}
   250  	defer rowIter.Close(ctx)
   251  	if _, err = rowIter.Next(ctx); err == nil {
   252  		return sql.ErrForeignKeyParentViolation.New(refActionData.ForeignKey.Name,
   253  			refActionData.ForeignKey.Table, refActionData.ForeignKey.ParentTable, refActionData.RowMapper.GetKeyString(row))
   254  	}
   255  	if err != io.EOF {
   256  		return err
   257  	}
   258  	return nil
   259  }
   260  
   261  // OnDeleteCascade handles the ON DELETE CASCADE referential action.
   262  func (fkEditor *ForeignKeyEditor) OnDeleteCascade(ctx *sql.Context, refActionData ForeignKeyRefActionData, row sql.Row, depth int) error {
   263  	rowIter, err := refActionData.RowMapper.GetIter(ctx, row, false)
   264  	if err != nil {
   265  		return err
   266  	}
   267  	defer rowIter.Close(ctx)
   268  	var rowToDelete sql.Row
   269  	for rowToDelete, err = rowIter.Next(ctx); err == nil; rowToDelete, err = rowIter.Next(ctx) {
   270  		// MySQL seems to have a bug where cyclical foreign keys return an error at a depth of 15 instead of 16.
   271  		// This replicates the observed behavior, regardless of whether we're replicating a bug or intentional behavior.
   272  		if depth >= 15 {
   273  			if fkEditor.Cyclical {
   274  				return sql.ErrForeignKeyDepthLimit.New()
   275  			} else if depth > 15 {
   276  				return sql.ErrForeignKeyDepthLimit.New()
   277  			}
   278  		}
   279  		err = refActionData.Editor.Delete(ctx, rowToDelete, depth)
   280  		if err != nil {
   281  			return err
   282  		}
   283  	}
   284  	if err == io.EOF {
   285  		return nil
   286  	}
   287  	return err
   288  }
   289  
   290  // OnDeleteSetNull handles the ON DELETE SET NULL referential action.
   291  func (fkEditor *ForeignKeyEditor) OnDeleteSetNull(ctx *sql.Context, refActionData ForeignKeyRefActionData, row sql.Row, depth int) error {
   292  	rowIter, err := refActionData.RowMapper.GetIter(ctx, row, false)
   293  	if err != nil {
   294  		return err
   295  	}
   296  	defer rowIter.Close(ctx)
   297  	var rowToNull sql.Row
   298  	for rowToNull, err = rowIter.Next(ctx); err == nil; rowToNull, err = rowIter.Next(ctx) {
   299  		// MySQL seems to have a bug where cyclical foreign keys return an error at a depth of 15 instead of 16.
   300  		// This replicates the observed behavior, regardless of whether we're replicating a bug or intentional behavior.
   301  		if depth >= 15 {
   302  			if fkEditor.Cyclical {
   303  				return sql.ErrForeignKeyDepthLimit.New()
   304  			} else if depth > 15 {
   305  				return sql.ErrForeignKeyDepthLimit.New()
   306  			}
   307  		}
   308  		nulledRow := make(sql.Row, len(rowToNull))
   309  		for i := range rowToNull {
   310  			// Row contents are nil by default, so we only need to assign the non-affected values
   311  			if refActionData.ChildParentMapping[i] == -1 {
   312  				nulledRow[i] = rowToNull[i]
   313  			}
   314  		}
   315  		err = refActionData.Editor.Update(ctx, rowToNull, nulledRow, depth)
   316  		if err != nil {
   317  			return err
   318  		}
   319  	}
   320  	if err == io.EOF {
   321  		return nil
   322  	}
   323  	return err
   324  }
   325  
   326  // ColumnsUpdated returns whether the columns involved in the foreign key were updated. Some updates may only update
   327  // columns that are not involved in a foreign key, and therefore we should ignore a CASCADE or SET NULL referential
   328  // action in such cases.
   329  func (fkEditor *ForeignKeyEditor) ColumnsUpdated(refActionData ForeignKeyRefActionData, old sql.Row, new sql.Row) (bool, error) {
   330  	for _, mappedVal := range refActionData.ChildParentMapping {
   331  		if mappedVal == -1 {
   332  			continue
   333  		}
   334  		oldVal := old[mappedVal]
   335  		newVal := new[mappedVal]
   336  		cmp, err := fkEditor.Schema[mappedVal].Type.Compare(oldVal, newVal)
   337  		if err != nil {
   338  			return false, err
   339  		}
   340  		if cmp != 0 {
   341  			return true, nil
   342  		}
   343  	}
   344  	return false, nil
   345  }
   346  
   347  // Close closes this handler along with all child handlers.
   348  func (fkEditor *ForeignKeyEditor) Close(ctx *sql.Context) error {
   349  	err := fkEditor.Editor.Close(ctx)
   350  	for _, child := range fkEditor.RefActions {
   351  		nErr := child.Editor.Close(ctx)
   352  		if err == nil {
   353  			err = nErr
   354  		}
   355  	}
   356  	return err
   357  }
   358  
   359  // ForeignKeyReferenceHandler handles references to any parent rows to verify they exist.
   360  type ForeignKeyReferenceHandler struct {
   361  	ForeignKey sql.ForeignKeyConstraint
   362  	RowMapper  ForeignKeyRowMapper
   363  	SelfCols   map[string]int // SelfCols are used for self-referential fks to refer to a col position given a col name
   364  }
   365  
   366  // IsInitialized returns whether this reference handler has been initialized.
   367  func (reference *ForeignKeyReferenceHandler) IsInitialized() bool {
   368  	return reference.RowMapper.IsInitialized()
   369  }
   370  
   371  // CheckReference checks that the given row has an index entry in the referenced table.
   372  func (reference *ForeignKeyReferenceHandler) CheckReference(ctx *sql.Context, row sql.Row) error {
   373  	// If even one of the values are NULL then we don't check the parent
   374  	for _, pos := range reference.RowMapper.IndexPositions {
   375  		if row[pos] == nil {
   376  			return nil
   377  		}
   378  	}
   379  
   380  	rowIter, err := reference.RowMapper.GetIter(ctx, row, true)
   381  	if err != nil {
   382  		return err
   383  	}
   384  	defer rowIter.Close(ctx)
   385  
   386  	_, err = rowIter.Next(ctx)
   387  	if err != nil && err != io.EOF {
   388  		return err
   389  	}
   390  	if err == nil {
   391  		// We have a parent row so throw no error
   392  		return nil
   393  	}
   394  
   395  	if reference.ForeignKey.IsSelfReferential() {
   396  		allMatch := true
   397  		for i := range reference.ForeignKey.Columns {
   398  			colPos := reference.SelfCols[reference.ForeignKey.Columns[i]]
   399  			refPos := reference.SelfCols[reference.ForeignKey.ParentColumns[i]]
   400  			cmp, err := reference.RowMapper.SourceSch[colPos].Type.Compare(row[colPos], row[refPos])
   401  			if err != nil {
   402  				return err
   403  			}
   404  			if cmp != 0 {
   405  				allMatch = false
   406  				break
   407  			}
   408  		}
   409  		if allMatch {
   410  			return nil
   411  		}
   412  	}
   413  	return sql.ErrForeignKeyChildViolation.New(reference.ForeignKey.Name, reference.ForeignKey.Table,
   414  		reference.ForeignKey.ParentTable, reference.RowMapper.GetKeyString(row))
   415  }
   416  
   417  // CheckTable checks that every row in the table has an index entry in the referenced table.
   418  func (reference *ForeignKeyReferenceHandler) CheckTable(ctx *sql.Context, tbl sql.ForeignKeyTable) error {
   419  	partIter, err := tbl.Partitions(ctx)
   420  	if err != nil {
   421  		return err
   422  	}
   423  	rowIter := sql.NewTableRowIter(ctx, tbl, partIter)
   424  	defer rowIter.Close(ctx)
   425  	for row, err := rowIter.Next(ctx); err == nil; row, err = rowIter.Next(ctx) {
   426  		err = reference.CheckReference(ctx, row)
   427  		if err != nil {
   428  			return err
   429  		}
   430  	}
   431  	if err != io.EOF {
   432  		return err
   433  	}
   434  	return nil
   435  }
   436  
   437  // ForeignKeyRowMapper takes a source row and returns all matching rows on the contained table according to the row
   438  // mapping from the source columns to the contained index's columns.
   439  type ForeignKeyRowMapper struct {
   440  	Index     sql.Index
   441  	Updater   sql.ForeignKeyEditor
   442  	SourceSch sql.Schema
   443  	// IndexPositions hold the mapping between an index's column position and the source row's column position. Given
   444  	// an index (x1, x2) and a source row (y1, y2, y3) and the relation (x1->y3, x2->y1), this slice would contain
   445  	// [2, 0]. The first index column "x1" maps to the third source column "y3" (so position 2 since it's zero-based),
   446  	// and the second index column "x2" maps to the first source column "y1" (position 0).
   447  	IndexPositions []int
   448  	// AppendTypes hold any types that may be needed to complete an index range's generation. Foreign keys are allowed
   449  	// to use an index's prefix, and indexes expect ranges to reference all of their columns (not just the prefix), so
   450  	// we grab the types of the suffix index columns to append to the range after the prefix columns that we're
   451  	// referencing.
   452  	AppendTypes []sql.Type
   453  }
   454  
   455  // IsInitialized returns whether this mapper has been initialized.
   456  func (mapper *ForeignKeyRowMapper) IsInitialized() bool {
   457  	return mapper.Updater != nil && mapper.Index != nil
   458  }
   459  
   460  // GetIter returns a row iterator for all rows that match the given source row.
   461  func (mapper *ForeignKeyRowMapper) GetIter(ctx *sql.Context, row sql.Row, refCheck bool) (sql.RowIter, error) {
   462  	rang := make(sql.Range, len(mapper.IndexPositions)+len(mapper.AppendTypes))
   463  	for rangPosition, rowPos := range mapper.IndexPositions {
   464  		rowVal := row[rowPos]
   465  		// If any value is NULL then it is ignored by foreign keys
   466  		if rowVal == nil {
   467  			return sql.RowsToRowIter(), nil
   468  		}
   469  		rang[rangPosition] = sql.ClosedRangeColumnExpr(rowVal, rowVal, mapper.SourceSch[rowPos].Type)
   470  	}
   471  	for i, appendType := range mapper.AppendTypes {
   472  		rang[i+len(mapper.IndexPositions)] = sql.AllRangeColumnExpr(appendType)
   473  	}
   474  
   475  	if !mapper.Index.CanSupport(rang) {
   476  		return nil, ErrInvalidLookupForIndexedTable.New(rang.DebugString())
   477  	}
   478  	//TODO: profile this, may need to redesign this or add a fast path
   479  	lookup := sql.IndexLookup{Ranges: []sql.Range{rang}, Index: mapper.Index}
   480  
   481  	editorData := mapper.Updater.IndexedAccess(lookup)
   482  
   483  	if rc, ok := editorData.(sql.ReferenceChecker); refCheck && ok {
   484  		err := rc.SetReferenceCheck()
   485  		if err != nil {
   486  			return nil, err
   487  		}
   488  	}
   489  
   490  	partIter, err := editorData.LookupPartitions(ctx, lookup)
   491  	if err != nil {
   492  		return nil, err
   493  	}
   494  	return sql.NewTableRowIter(ctx, editorData, partIter), nil
   495  }
   496  
   497  // GetKeyString returns a string representing the key used to access the index.
   498  func (mapper *ForeignKeyRowMapper) GetKeyString(row sql.Row) string {
   499  	keyStrParts := make([]string, len(mapper.IndexPositions))
   500  	for i, rowPos := range mapper.IndexPositions {
   501  		keyStrParts[i] = fmt.Sprint(row[rowPos])
   502  	}
   503  	return fmt.Sprintf("[%s]", strings.Join(keyStrParts, ","))
   504  }
   505  
   506  // GetChildParentMapping returns a mapping from the foreign key columns of a child schema to the parent schema.
   507  func GetChildParentMapping(parentSch sql.Schema, childSch sql.Schema, fkDef sql.ForeignKeyConstraint) (ChildParentMapping, error) {
   508  	parentMap := make(map[string]int)
   509  	for i, col := range parentSch {
   510  		parentMap[strings.ToLower(col.Name)] = i
   511  	}
   512  	childMap := make(map[string]int)
   513  	for i, col := range childSch {
   514  		childMap[strings.ToLower(col.Name)] = i
   515  	}
   516  	mapping := make(ChildParentMapping, len(childSch))
   517  	for i := range mapping {
   518  		mapping[i] = -1
   519  	}
   520  	for i := range fkDef.Columns {
   521  		childIndex, ok := childMap[strings.ToLower(fkDef.Columns[i])]
   522  		if !ok {
   523  			return nil, fmt.Errorf("foreign key `%s` refers to column `%s` on table `%s` but it could not be found",
   524  				fkDef.Name, fkDef.Columns[i], fkDef.Table)
   525  		}
   526  		parentIndex, ok := parentMap[strings.ToLower(fkDef.ParentColumns[i])]
   527  		if !ok {
   528  			return nil, fmt.Errorf("foreign key `%s` refers to column `%s` on referenced table `%s` but it could not be found",
   529  				fkDef.Name, fkDef.ParentColumns[i], fkDef.ParentTable)
   530  		}
   531  		mapping[childIndex] = parentIndex
   532  	}
   533  	return mapping, nil
   534  }