github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/merge/violations_fk.go (about)

     1  // Copyright 2021 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 merge
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"io"
    22  	"time"
    23  
    24  	gmstypes "github.com/dolthub/go-mysql-server/sql/types"
    25  
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/diff"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    28  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable"
    29  	"github.com/dolthub/dolt/go/libraries/doltcore/row"
    30  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    31  	json2 "github.com/dolthub/dolt/go/libraries/doltcore/sqle/json"
    32  	"github.com/dolthub/dolt/go/libraries/doltcore/table"
    33  	"github.com/dolthub/dolt/go/libraries/doltcore/table/typed/noms"
    34  	"github.com/dolthub/dolt/go/libraries/utils/set"
    35  	diff2 "github.com/dolthub/dolt/go/store/diff"
    36  	"github.com/dolthub/dolt/go/store/hash"
    37  	"github.com/dolthub/dolt/go/store/prolly"
    38  	"github.com/dolthub/dolt/go/store/types"
    39  	"github.com/dolthub/dolt/go/store/val"
    40  )
    41  
    42  // constraintViolationsLoadedTable is a collection of items needed to process constraint violations for a single table.
    43  type constraintViolationsLoadedTable struct {
    44  	TableName   string
    45  	Table       *doltdb.Table
    46  	Schema      schema.Schema
    47  	RowData     durable.Index
    48  	Index       schema.Index
    49  	IndexSchema schema.Schema
    50  	IndexData   durable.Index
    51  }
    52  
    53  // cvType is an enum for a constraint violation type.
    54  type CvType uint64
    55  
    56  const (
    57  	CvType_ForeignKey CvType = iota + 1
    58  	CvType_UniqueIndex
    59  	CvType_CheckConstraint
    60  	CvType_NotNull
    61  )
    62  
    63  type FKViolationReceiver interface {
    64  	StartFK(ctx context.Context, fk doltdb.ForeignKey) error
    65  	EndCurrFK(ctx context.Context) error
    66  	NomsFKViolationFound(ctx context.Context, rowKey, rowValue types.Tuple) error
    67  	ProllyFKViolationFound(ctx context.Context, rowKey, rowValue val.Tuple) error
    68  }
    69  
    70  // GetForeignKeyViolations returns the violations that have been created as a
    71  // result of the diff between |baseRoot| and |newRoot|. It sends the violations to |receiver|.
    72  func GetForeignKeyViolations(ctx context.Context, newRoot, baseRoot doltdb.RootValue, tables *set.StrSet, receiver FKViolationReceiver) error {
    73  	fkColl, err := newRoot.GetForeignKeyCollection(ctx)
    74  	if err != nil {
    75  		return err
    76  	}
    77  	for _, foreignKey := range fkColl.AllKeys() {
    78  		if !foreignKey.IsResolved() || (tables.Size() != 0 && !tables.Contains(foreignKey.TableName)) {
    79  			continue
    80  		}
    81  
    82  		err = receiver.StartFK(ctx, foreignKey)
    83  		if err != nil {
    84  			return err
    85  		}
    86  
    87  		postParent, ok, err := newConstraintViolationsLoadedTable(ctx, foreignKey.ReferencedTableName, foreignKey.ReferencedTableIndex, newRoot)
    88  		if err != nil {
    89  			return err
    90  		}
    91  		if !ok {
    92  			return fmt.Errorf("foreign key %s should have index %s on table %s but it cannot be found",
    93  				foreignKey.Name, foreignKey.ReferencedTableIndex, foreignKey.ReferencedTableName)
    94  		}
    95  
    96  		postChild, ok, err := newConstraintViolationsLoadedTable(ctx, foreignKey.TableName, foreignKey.TableIndex, newRoot)
    97  		if err != nil {
    98  			return err
    99  		}
   100  		if !ok {
   101  			return fmt.Errorf("foreign key %s should have index %s on table %s but it cannot be found",
   102  				foreignKey.Name, foreignKey.TableIndex, foreignKey.TableName)
   103  		}
   104  
   105  		preParent, _, err := newConstraintViolationsLoadedTable(ctx, foreignKey.ReferencedTableName, foreignKey.ReferencedTableIndex, baseRoot)
   106  		if err != nil {
   107  			if err != doltdb.ErrTableNotFound {
   108  				return err
   109  			}
   110  			// Parent does not exist in the ancestor so we use an empty map
   111  			emptyIdx, err := durable.NewEmptyIndex(ctx, postParent.Table.ValueReadWriter(), postParent.Table.NodeStore(), postParent.Schema)
   112  			if err != nil {
   113  				return err
   114  			}
   115  			err = parentFkConstraintViolations(ctx, baseRoot.VRW(), foreignKey, postParent, postParent, postChild, emptyIdx, receiver)
   116  			if err != nil {
   117  				return err
   118  			}
   119  		} else {
   120  			// Parent exists in the ancestor
   121  			err = parentFkConstraintViolations(ctx, baseRoot.VRW(), foreignKey, preParent, postParent, postChild, preParent.RowData, receiver)
   122  			if err != nil {
   123  				return err
   124  			}
   125  		}
   126  
   127  		preChild, _, err := newConstraintViolationsLoadedTable(ctx, foreignKey.TableName, foreignKey.TableIndex, baseRoot)
   128  		if err != nil {
   129  			if err != doltdb.ErrTableNotFound {
   130  				return err
   131  			}
   132  			// Child does not exist in the ancestor so we use an empty map
   133  			emptyIdx, err := durable.NewEmptyIndex(ctx, postChild.Table.ValueReadWriter(), postChild.Table.NodeStore(), postChild.Schema)
   134  			if err != nil {
   135  				return err
   136  			}
   137  
   138  			err = childFkConstraintViolations(ctx, baseRoot.VRW(), foreignKey, postParent, postChild, postChild, emptyIdx, receiver)
   139  			if err != nil {
   140  				return err
   141  			}
   142  		} else {
   143  			err = childFkConstraintViolations(ctx, baseRoot.VRW(), foreignKey, postParent, postChild, preChild, preChild.RowData, receiver)
   144  			if err != nil {
   145  				return err
   146  			}
   147  		}
   148  
   149  		err = receiver.EndCurrFK(ctx)
   150  		if err != nil {
   151  			return err
   152  		}
   153  	}
   154  	return nil
   155  }
   156  
   157  // AddForeignKeyViolations adds foreign key constraint violations to each table.
   158  // todo(andy): pass doltdb.Rootish
   159  func AddForeignKeyViolations(ctx context.Context, newRoot, baseRoot doltdb.RootValue, tables *set.StrSet, theirRootIsh hash.Hash) (doltdb.RootValue, *set.StrSet, error) {
   160  	violationWriter := &foreignKeyViolationWriter{rootValue: newRoot, theirRootIsh: theirRootIsh, violatedTables: set.NewStrSet(nil)}
   161  	err := GetForeignKeyViolations(ctx, newRoot, baseRoot, tables, violationWriter)
   162  	if err != nil {
   163  		return nil, nil, err
   164  	}
   165  	return violationWriter.rootValue, violationWriter.violatedTables, nil
   166  }
   167  
   168  // GetForeignKeyViolatedTables returns a list of tables that have foreign key
   169  // violations based on the diff between |newRoot| and |baseRoot|.
   170  func GetForeignKeyViolatedTables(ctx context.Context, newRoot, baseRoot doltdb.RootValue, tables *set.StrSet) (*set.StrSet, error) {
   171  	handler := &foreignKeyViolationTracker{tableSet: set.NewStrSet(nil)}
   172  	err := GetForeignKeyViolations(ctx, newRoot, baseRoot, tables, handler)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  	return handler.tableSet, nil
   177  }
   178  
   179  // foreignKeyViolationTracker tracks which tables have foreign key violations
   180  type foreignKeyViolationTracker struct {
   181  	tableSet *set.StrSet
   182  	currFk   doltdb.ForeignKey
   183  }
   184  
   185  func (f *foreignKeyViolationTracker) StartFK(ctx context.Context, fk doltdb.ForeignKey) error {
   186  	f.currFk = fk
   187  	return nil
   188  }
   189  
   190  func (f *foreignKeyViolationTracker) EndCurrFK(ctx context.Context) error {
   191  	return nil
   192  }
   193  
   194  func (f *foreignKeyViolationTracker) NomsFKViolationFound(ctx context.Context, rowKey, rowValue types.Tuple) error {
   195  	f.tableSet.Add(f.currFk.TableName)
   196  	return nil
   197  }
   198  
   199  func (f *foreignKeyViolationTracker) ProllyFKViolationFound(ctx context.Context, rowKey, rowValue val.Tuple) error {
   200  	f.tableSet.Add(f.currFk.TableName)
   201  	return nil
   202  }
   203  
   204  var _ FKViolationReceiver = (*foreignKeyViolationTracker)(nil)
   205  
   206  // foreignKeyViolationWriter updates rootValue with the foreign key constraint violations.
   207  type foreignKeyViolationWriter struct {
   208  	rootValue      doltdb.RootValue
   209  	theirRootIsh   hash.Hash
   210  	violatedTables *set.StrSet
   211  
   212  	currFk  doltdb.ForeignKey
   213  	currTbl *doltdb.Table
   214  
   215  	// prolly
   216  	artEditor     *prolly.ArtifactsEditor
   217  	kd            val.TupleDesc
   218  	cInfoJsonData []byte
   219  
   220  	// noms
   221  	violMapEditor *types.MapEditor
   222  	nomsVInfo     types.JSON
   223  }
   224  
   225  var _ FKViolationReceiver = (*foreignKeyViolationWriter)(nil)
   226  
   227  func (f *foreignKeyViolationWriter) StartFK(ctx context.Context, fk doltdb.ForeignKey) error {
   228  	f.currFk = fk
   229  
   230  	tbl, ok, err := f.rootValue.GetTable(ctx, doltdb.TableName{Name: fk.TableName})
   231  	if err != nil {
   232  		return err
   233  	}
   234  	if !ok {
   235  		return doltdb.ErrTableNotFound
   236  	}
   237  
   238  	f.currTbl = tbl
   239  
   240  	refTbl, ok, err := f.rootValue.GetTable(ctx, doltdb.TableName{Name: fk.ReferencedTableName})
   241  	if err != nil {
   242  		return err
   243  	}
   244  	if !ok {
   245  		return doltdb.ErrTableNotFound
   246  	}
   247  
   248  	sch, err := tbl.GetSchema(ctx)
   249  	if err != nil {
   250  		return err
   251  	}
   252  
   253  	refSch, err := refTbl.GetSchema(ctx)
   254  	if err != nil {
   255  		return err
   256  	}
   257  
   258  	jsonData, err := foreignKeyCVJson(fk, sch, refSch)
   259  	if err != nil {
   260  		return err
   261  	}
   262  
   263  	if types.IsFormat_DOLT(tbl.Format()) {
   264  		arts, err := tbl.GetArtifacts(ctx)
   265  		if err != nil {
   266  			return err
   267  		}
   268  		artMap := durable.ProllyMapFromArtifactIndex(arts)
   269  		f.artEditor = artMap.Editor()
   270  		f.cInfoJsonData = jsonData
   271  		f.kd = sch.GetKeyDescriptor()
   272  	} else {
   273  		violMap, err := tbl.GetConstraintViolations(ctx)
   274  		if err != nil {
   275  			return err
   276  		}
   277  		f.violMapEditor = violMap.Edit()
   278  
   279  		f.nomsVInfo, err = jsonDataToNomsValue(ctx, tbl.ValueReadWriter(), jsonData)
   280  		if err != nil {
   281  			return err
   282  		}
   283  	}
   284  
   285  	return nil
   286  }
   287  
   288  func (f *foreignKeyViolationWriter) EndCurrFK(ctx context.Context) error {
   289  	if types.IsFormat_DOLT(f.currTbl.Format()) {
   290  		artMap, err := f.artEditor.Flush(ctx)
   291  		if err != nil {
   292  			return err
   293  		}
   294  		artIdx := durable.ArtifactIndexFromProllyMap(artMap)
   295  		tbl, err := f.currTbl.SetArtifacts(ctx, artIdx)
   296  		if err != nil {
   297  			return err
   298  		}
   299  		f.rootValue, err = f.rootValue.PutTable(ctx, doltdb.TableName{Name: f.currFk.TableName}, tbl)
   300  		if err != nil {
   301  			return err
   302  		}
   303  		return nil
   304  	}
   305  
   306  	violMap, err := f.violMapEditor.Map(ctx)
   307  	if err != nil {
   308  		return err
   309  	}
   310  	tbl, err := f.currTbl.SetConstraintViolations(ctx, violMap)
   311  	if err != nil {
   312  		return err
   313  	}
   314  	f.rootValue, err = f.rootValue.PutTable(ctx, doltdb.TableName{Name: f.currFk.TableName}, tbl)
   315  	if err != nil {
   316  		return err
   317  	}
   318  	return nil
   319  }
   320  
   321  func (f *foreignKeyViolationWriter) NomsFKViolationFound(ctx context.Context, rowKey, rowValue types.Tuple) error {
   322  
   323  	cvKey, cvVal, err := toConstraintViolationRow(ctx, CvType_ForeignKey, f.nomsVInfo, rowKey, rowValue)
   324  	if err != nil {
   325  		return err
   326  	}
   327  
   328  	f.violMapEditor.Set(cvKey, cvVal)
   329  
   330  	f.violatedTables.Add(f.currFk.TableName)
   331  
   332  	return nil
   333  }
   334  
   335  func (f *foreignKeyViolationWriter) ProllyFKViolationFound(ctx context.Context, rowKey, rowValue val.Tuple) error {
   336  
   337  	meta := prolly.ConstraintViolationMeta{VInfo: f.cInfoJsonData, Value: rowValue}
   338  
   339  	err := f.artEditor.ReplaceConstraintViolation(ctx, rowKey, f.theirRootIsh, prolly.ArtifactTypeForeignKeyViol, meta)
   340  	if err != nil {
   341  		return err
   342  	}
   343  
   344  	f.violatedTables.Add(f.currFk.TableName)
   345  
   346  	return nil
   347  }
   348  
   349  var _ FKViolationReceiver = (*foreignKeyViolationWriter)(nil)
   350  
   351  // parentFkConstraintViolations processes foreign key constraint violations for the parent in a foreign key.
   352  func parentFkConstraintViolations(
   353  	ctx context.Context,
   354  	vr types.ValueReader,
   355  	foreignKey doltdb.ForeignKey,
   356  	preParent, postParent, postChild *constraintViolationsLoadedTable,
   357  	preParentRowData durable.Index,
   358  	receiver FKViolationReceiver,
   359  ) error {
   360  	if preParentRowData.Format() != types.Format_DOLT {
   361  		m := durable.NomsMapFromIndex(preParentRowData)
   362  		return nomsParentFkConstraintViolations(ctx, vr, foreignKey, postParent, postChild, preParent.Schema, m, receiver)
   363  	}
   364  	if preParent.IndexData == nil || postParent.Schema.GetPKCols().Size() == 0 || preParent.Schema.GetPKCols().Size() == 0 {
   365  		m := durable.ProllyMapFromIndex(preParentRowData)
   366  		return prollyParentPriDiffFkConstraintViolations(ctx, foreignKey, postParent, postChild, m, receiver)
   367  	}
   368  	empty, err := preParentRowData.Empty()
   369  	if err != nil {
   370  		return err
   371  	}
   372  	var idx durable.Index
   373  	if empty {
   374  		idx, err = durable.NewEmptyIndex(ctx, postChild.Table.ValueReadWriter(), postParent.Table.NodeStore(), postParent.Schema)
   375  		if err != nil {
   376  			return err
   377  		}
   378  	} else {
   379  		idx = preParent.IndexData
   380  	}
   381  	m := durable.ProllyMapFromIndex(idx)
   382  	return prollyParentSecDiffFkConstraintViolations(ctx, foreignKey, postParent, postChild, m, receiver)
   383  }
   384  
   385  // childFkConstraintViolations handles processing the reference options on a child, or creating a violation if
   386  // necessary.
   387  func childFkConstraintViolations(
   388  	ctx context.Context,
   389  	vr types.ValueReader,
   390  	foreignKey doltdb.ForeignKey,
   391  	postParent, postChild, preChild *constraintViolationsLoadedTable,
   392  	preChildRowData durable.Index,
   393  	receiver FKViolationReceiver,
   394  ) error {
   395  	if preChildRowData.Format() != types.Format_DOLT {
   396  		m := durable.NomsMapFromIndex(preChildRowData)
   397  		return nomsChildFkConstraintViolations(ctx, vr, foreignKey, postParent, postChild, preChild.Schema, m, receiver)
   398  	}
   399  	if preChild.IndexData == nil || postChild.Schema.GetPKCols().Size() == 0 || preChild.Schema.GetPKCols().Size() == 0 {
   400  		m := durable.ProllyMapFromIndex(preChildRowData)
   401  		return prollyChildPriDiffFkConstraintViolations(ctx, foreignKey, postParent, postChild, m, receiver)
   402  	}
   403  	empty, err := preChildRowData.Empty()
   404  	if err != nil {
   405  		return err
   406  	}
   407  	var idx durable.Index
   408  	if empty {
   409  		idx, err = durable.NewEmptyIndex(ctx, postChild.Table.ValueReadWriter(), postChild.Table.NodeStore(), postChild.Schema)
   410  		if err != nil {
   411  			return err
   412  		}
   413  	} else {
   414  		idx = preChild.IndexData
   415  	}
   416  	m := durable.ProllyMapFromIndex(idx)
   417  	return prollyChildSecDiffFkConstraintViolations(ctx, foreignKey, postParent, postChild, m, receiver)
   418  }
   419  
   420  func nomsParentFkConstraintViolations(
   421  	ctx context.Context,
   422  	vr types.ValueReader,
   423  	foreignKey doltdb.ForeignKey,
   424  	postParent, postChild *constraintViolationsLoadedTable,
   425  	preParentSch schema.Schema,
   426  	preParentRowData types.Map,
   427  	receiver FKViolationReceiver) error {
   428  
   429  	postParentIndexTags := postParent.Index.IndexedColumnTags()
   430  	postChildIndexTags := postChild.Index.IndexedColumnTags()
   431  
   432  	differ := diff.NewRowDiffer(ctx, preParentRowData.Format(), preParentSch, postParent.Schema, 1024)
   433  	defer differ.Close()
   434  	differ.Start(ctx, preParentRowData, durable.NomsMapFromIndex(postParent.RowData))
   435  	for {
   436  		diffSlice, hasMore, err := differ.GetDiffs(1, 10*time.Second)
   437  		if err != nil {
   438  			return err
   439  		}
   440  		if len(diffSlice) != 1 {
   441  			if hasMore {
   442  				return fmt.Errorf("no diff returned but should have errored earlier")
   443  			}
   444  			break
   445  		}
   446  		rowDiff := diffSlice[0]
   447  		switch rowDiff.ChangeType {
   448  		case types.DiffChangeRemoved, types.DiffChangeModified:
   449  			postParentRow, err := row.FromNoms(postParent.Schema, rowDiff.KeyValue.(types.Tuple), rowDiff.OldValue.(types.Tuple))
   450  			if err != nil {
   451  				return err
   452  			}
   453  			hasNulls := false
   454  			for _, tag := range postParentIndexTags {
   455  				if postParentRowEntry, ok := postParentRow.GetColVal(tag); !ok || types.IsNull(postParentRowEntry) {
   456  					hasNulls = true
   457  					break
   458  				}
   459  			}
   460  			if hasNulls {
   461  				continue
   462  			}
   463  
   464  			postParentIndexPartialKey, err := row.ReduceToIndexPartialKey(foreignKey.TableColumns, postParent.Index, postParentRow)
   465  			if err != nil {
   466  				return err
   467  			}
   468  
   469  			shouldContinue, err := func() (bool, error) {
   470  				var mapIter table.ReadCloser = noms.NewNomsRangeReader(
   471  					vr,
   472  					postParent.IndexSchema,
   473  					durable.NomsMapFromIndex(postParent.IndexData),
   474  					[]*noms.ReadRange{{Start: postParentIndexPartialKey, Inclusive: true, Reverse: false, Check: noms.InRangeCheckPartial(postParentIndexPartialKey)}})
   475  				defer mapIter.Close(ctx)
   476  				if _, err := mapIter.ReadRow(ctx); err == nil {
   477  					// If the parent table has other rows that satisfy the partial key then we choose to do nothing
   478  					return true, nil
   479  				} else if err != io.EOF {
   480  					return false, err
   481  				}
   482  				return false, nil
   483  			}()
   484  			if err != nil {
   485  				return err
   486  			}
   487  			if shouldContinue {
   488  				continue
   489  			}
   490  
   491  			postParentIndexPartialKeySlice, err := postParentIndexPartialKey.AsSlice()
   492  			if err != nil {
   493  				return err
   494  			}
   495  			for i := 0; i < len(postChildIndexTags); i++ {
   496  				postParentIndexPartialKeySlice[2*i] = types.Uint(postChildIndexTags[i])
   497  			}
   498  			postChildIndexPartialKey, err := types.NewTuple(postChild.Table.Format(), postParentIndexPartialKeySlice...)
   499  			if err != nil {
   500  				return err
   501  			}
   502  			err = nomsParentFkConstraintViolationsProcess(ctx, vr, foreignKey, postChild, postChildIndexPartialKey, receiver)
   503  			if err != nil {
   504  				return err
   505  			}
   506  		case types.DiffChangeAdded:
   507  			// We don't do anything if a parent row was added
   508  		default:
   509  			return fmt.Errorf("unknown diff change type")
   510  		}
   511  		if !hasMore {
   512  			break
   513  		}
   514  	}
   515  
   516  	return nil
   517  }
   518  
   519  func nomsParentFkConstraintViolationsProcess(
   520  	ctx context.Context,
   521  	vr types.ValueReader,
   522  	foreignKey doltdb.ForeignKey,
   523  	postChild *constraintViolationsLoadedTable,
   524  	postChildIndexPartialKey types.Tuple,
   525  	receiver FKViolationReceiver,
   526  ) error {
   527  	indexData := durable.NomsMapFromIndex(postChild.IndexData)
   528  	rowData := durable.NomsMapFromIndex(postChild.RowData)
   529  
   530  	mapIter := noms.NewNomsRangeReader(
   531  		vr,
   532  		postChild.IndexSchema,
   533  		indexData,
   534  		[]*noms.ReadRange{{Start: postChildIndexPartialKey, Inclusive: true, Reverse: false, Check: noms.InRangeCheckPartial(postChildIndexPartialKey)}})
   535  	defer mapIter.Close(ctx)
   536  	var postChildIndexRow row.Row
   537  	var err error
   538  	for postChildIndexRow, err = mapIter.ReadRow(ctx); err == nil; postChildIndexRow, err = mapIter.ReadRow(ctx) {
   539  		postChildIndexKey, err := postChildIndexRow.NomsMapKey(postChild.IndexSchema).Value(ctx)
   540  		if err != nil {
   541  			return err
   542  		}
   543  		postChildRowKey, err := postChild.Index.ToTableTuple(ctx, postChildIndexKey.(types.Tuple), postChild.Table.Format())
   544  		if err != nil {
   545  			return err
   546  		}
   547  		postChildRowVal, ok, err := rowData.MaybeGetTuple(ctx, postChildRowKey)
   548  		if err != nil {
   549  			return err
   550  		}
   551  		if !ok {
   552  			return fmt.Errorf("index %s on %s contains data that table does not", foreignKey.TableIndex, foreignKey.TableName)
   553  		}
   554  
   555  		err = receiver.NomsFKViolationFound(ctx, postChildRowKey, postChildRowVal)
   556  		if err != nil {
   557  			return err
   558  		}
   559  	}
   560  	if err != io.EOF {
   561  		return err
   562  	}
   563  	return nil
   564  }
   565  
   566  // nomsChildFkConstraintViolations processes foreign key constraint violations for the child in a foreign key.
   567  func nomsChildFkConstraintViolations(
   568  	ctx context.Context,
   569  	vr types.ValueReader,
   570  	foreignKey doltdb.ForeignKey,
   571  	postParent, postChild *constraintViolationsLoadedTable,
   572  	preChildSch schema.Schema,
   573  	preChildRowData types.Map,
   574  	receiver FKViolationReceiver,
   575  ) error {
   576  	var postParentIndexTags, postChildIndexTags []uint64
   577  	if postParent.Index.Name() == "" {
   578  		postParentIndexTags = foreignKey.ReferencedTableColumns
   579  		postChildIndexTags = foreignKey.TableColumns
   580  	} else {
   581  		postParentIndexTags = postParent.Index.IndexedColumnTags()
   582  		postChildIndexTags = postChild.Index.IndexedColumnTags()
   583  	}
   584  
   585  	differ := diff.NewRowDiffer(ctx, preChildRowData.Format(), preChildSch, postChild.Schema, 1024)
   586  	defer differ.Close()
   587  	differ.Start(ctx, preChildRowData, durable.NomsMapFromIndex(postChild.RowData))
   588  	for {
   589  		diffSlice, hasMore, err := differ.GetDiffs(1, 10*time.Second)
   590  		if err != nil {
   591  			return err
   592  		}
   593  		if len(diffSlice) != 1 {
   594  			if hasMore {
   595  				return fmt.Errorf("no diff returned but should have errored earlier")
   596  			}
   597  			break
   598  		}
   599  		rowDiff := diffSlice[0]
   600  		switch rowDiff.ChangeType {
   601  		case types.DiffChangeAdded, types.DiffChangeModified:
   602  			postChildRow, err := row.FromNoms(postChild.Schema, rowDiff.KeyValue.(types.Tuple), rowDiff.NewValue.(types.Tuple))
   603  			if err != nil {
   604  				return err
   605  			}
   606  			hasNulls := false
   607  			for _, tag := range postChildIndexTags {
   608  				if postChildRowEntry, ok := postChildRow.GetColVal(tag); !ok || types.IsNull(postChildRowEntry) {
   609  					hasNulls = true
   610  					break
   611  				}
   612  			}
   613  			if hasNulls {
   614  				continue
   615  			}
   616  
   617  			postChildIndexPartialKey, err := row.ReduceToIndexPartialKey(postChildIndexTags, postChild.Index, postChildRow)
   618  			if err != nil {
   619  				return err
   620  			}
   621  			postChildIndexPartialKeySlice, err := postChildIndexPartialKey.AsSlice()
   622  			if err != nil {
   623  				return err
   624  			}
   625  			for i := 0; i < len(postParentIndexTags); i++ {
   626  				postChildIndexPartialKeySlice[2*i] = types.Uint(postParentIndexTags[i])
   627  			}
   628  			parentPartialKey, err := types.NewTuple(postChild.Table.Format(), postChildIndexPartialKeySlice...)
   629  			if err != nil {
   630  				return err
   631  			}
   632  			err = childFkConstraintViolationsProcess(ctx, vr, postParent, rowDiff, parentPartialKey, receiver)
   633  			if err != nil {
   634  				return err
   635  			}
   636  		case types.DiffChangeRemoved:
   637  			// We don't do anything if a child row was removed
   638  		default:
   639  			return fmt.Errorf("unknown diff change type")
   640  		}
   641  		if !hasMore {
   642  			break
   643  		}
   644  	}
   645  
   646  	return nil
   647  }
   648  
   649  // childFkConstraintViolationsProcess handles processing the constraint violations for the child of a foreign key.
   650  func childFkConstraintViolationsProcess(
   651  	ctx context.Context,
   652  	vr types.ValueReader,
   653  	postParent *constraintViolationsLoadedTable,
   654  	rowDiff *diff2.Difference,
   655  	parentPartialKey types.Tuple,
   656  	receiver FKViolationReceiver,
   657  ) error {
   658  	var mapIter table.ReadCloser = noms.NewNomsRangeReader(
   659  		vr,
   660  		postParent.IndexSchema,
   661  		durable.NomsMapFromIndex(postParent.IndexData),
   662  		[]*noms.ReadRange{{Start: parentPartialKey, Inclusive: true, Reverse: false, Check: noms.InRangeCheckPartial(parentPartialKey)}})
   663  	defer mapIter.Close(ctx)
   664  	// If the row exists in the parent, then we don't need to do anything
   665  	if _, err := mapIter.ReadRow(ctx); err != nil {
   666  		if err != io.EOF {
   667  			return err
   668  		}
   669  		err = receiver.NomsFKViolationFound(ctx, rowDiff.KeyValue.(types.Tuple), rowDiff.NewValue.(types.Tuple))
   670  		if err != nil {
   671  			return err
   672  		}
   673  		return nil
   674  	}
   675  	return nil
   676  }
   677  
   678  // newConstraintViolationsLoadedTable returns a *constraintViolationsLoadedTable. Returns false if the table was loaded
   679  // but the index could not be found. If the table could not be found, then an error is returned.
   680  func newConstraintViolationsLoadedTable(ctx context.Context, tblName, idxName string, root doltdb.RootValue) (*constraintViolationsLoadedTable, bool, error) {
   681  	tbl, trueTblName, ok, err := doltdb.GetTableInsensitive(ctx, root, tblName)
   682  	if err != nil {
   683  		return nil, false, err
   684  	}
   685  	if !ok {
   686  		return nil, false, doltdb.ErrTableNotFound
   687  	}
   688  	sch, err := tbl.GetSchema(ctx)
   689  	if err != nil {
   690  		return nil, false, err
   691  	}
   692  	rowData, err := tbl.GetRowData(ctx)
   693  	if err != nil {
   694  		return nil, false, err
   695  	}
   696  
   697  	// Create Primary Key Index
   698  	if idxName == "" {
   699  		pkCols := sch.GetPKCols()
   700  		pkIdxColl := schema.NewIndexCollection(pkCols, pkCols)
   701  		pkIdxProps := schema.IndexProperties{
   702  			IsUnique:      true,
   703  			IsUserDefined: false,
   704  			Comment:       "",
   705  		}
   706  		pkIdx := schema.NewIndex("", pkCols.Tags, pkCols.Tags, pkIdxColl, pkIdxProps)
   707  		return &constraintViolationsLoadedTable{
   708  			TableName:   trueTblName,
   709  			Table:       tbl,
   710  			Schema:      sch,
   711  			RowData:     rowData,
   712  			Index:       pkIdx,
   713  			IndexSchema: pkIdx.Schema(),
   714  			IndexData:   rowData,
   715  		}, true, nil
   716  	}
   717  
   718  	idx, ok := sch.Indexes().GetByNameCaseInsensitive(idxName)
   719  	if !ok {
   720  		return &constraintViolationsLoadedTable{
   721  			TableName: trueTblName,
   722  			Table:     tbl,
   723  			Schema:    sch,
   724  			RowData:   rowData,
   725  		}, false, nil
   726  	}
   727  	indexData, err := tbl.GetIndexRowData(ctx, idx.Name())
   728  	if err != nil {
   729  		return nil, false, err
   730  	}
   731  	return &constraintViolationsLoadedTable{
   732  		TableName:   trueTblName,
   733  		Table:       tbl,
   734  		Schema:      sch,
   735  		RowData:     rowData,
   736  		Index:       idx,
   737  		IndexSchema: idx.Schema(),
   738  		IndexData:   indexData,
   739  	}, true, nil
   740  }
   741  
   742  // toConstraintViolationRow converts the given key and value tuples into ones suitable to add to a constraint violation map.
   743  func toConstraintViolationRow(ctx context.Context, vType CvType, vInfo types.JSON, k, v types.Tuple) (types.Tuple, types.Tuple, error) {
   744  	constraintViolationKeyVals := []types.Value{types.Uint(schema.DoltConstraintViolationsTypeTag), types.Uint(vType)}
   745  	keySlice, err := k.AsSlice()
   746  	if err != nil {
   747  		emptyTuple := types.EmptyTuple(k.Format())
   748  		return emptyTuple, emptyTuple, err
   749  	}
   750  	constraintViolationKeyVals = append(constraintViolationKeyVals, keySlice...)
   751  	constraintViolationKey, err := types.NewTuple(k.Format(), constraintViolationKeyVals...)
   752  	if err != nil {
   753  		emptyTuple := types.EmptyTuple(k.Format())
   754  		return emptyTuple, emptyTuple, err
   755  	}
   756  
   757  	constraintViolationValVals, err := v.AsSlice()
   758  	if err != nil {
   759  		emptyTuple := types.EmptyTuple(k.Format())
   760  		return emptyTuple, emptyTuple, err
   761  	}
   762  	constraintViolationValVals = append(constraintViolationValVals, types.Uint(schema.DoltConstraintViolationsInfoTag), vInfo)
   763  	constraintViolationVal, err := types.NewTuple(v.Format(), constraintViolationValVals...)
   764  	if err != nil {
   765  		emptyTuple := types.EmptyTuple(k.Format())
   766  		return emptyTuple, emptyTuple, err
   767  	}
   768  
   769  	return constraintViolationKey, constraintViolationVal, nil
   770  }
   771  
   772  // foreignKeyCVJson converts a foreign key to JSON data for use as the info field in a constraint violations map.
   773  func foreignKeyCVJson(foreignKey doltdb.ForeignKey, sch, refSch schema.Schema) ([]byte, error) {
   774  	schCols := sch.GetAllCols()
   775  	refSchCols := refSch.GetAllCols()
   776  	fkCols := make([]string, len(foreignKey.TableColumns))
   777  	refFkCols := make([]string, len(foreignKey.ReferencedTableColumns))
   778  	for i, tag := range foreignKey.TableColumns {
   779  		if col, ok := schCols.TagToCol[tag]; !ok {
   780  			return nil, fmt.Errorf("foreign key '%s' references tag '%d' on table '%s' but it cannot be found",
   781  				foreignKey.Name, tag, foreignKey.TableName)
   782  		} else {
   783  			fkCols[i] = col.Name
   784  		}
   785  	}
   786  	for i, tag := range foreignKey.ReferencedTableColumns {
   787  		if col, ok := refSchCols.TagToCol[tag]; !ok {
   788  			return nil, fmt.Errorf("foreign key '%s' references tag '%d' on table '%s' but it cannot be found",
   789  				foreignKey.Name, tag, foreignKey.ReferencedTableName)
   790  		} else {
   791  			refFkCols[i] = col.Name
   792  		}
   793  	}
   794  
   795  	m := FkCVMeta{
   796  		Columns:           fkCols,
   797  		ForeignKey:        foreignKey.Name,
   798  		Index:             foreignKey.TableIndex,
   799  		OnDelete:          foreignKey.OnDelete.ReducedString(),
   800  		OnUpdate:          foreignKey.OnUpdate.ReducedString(),
   801  		ReferencedColumns: refFkCols,
   802  		ReferencedIndex:   foreignKey.ReferencedTableIndex,
   803  		ReferencedTable:   foreignKey.ReferencedTableName,
   804  		Table:             foreignKey.TableName,
   805  	}
   806  	d, err := json.Marshal(m)
   807  	if err != nil {
   808  		return nil, err
   809  	}
   810  
   811  	return d, nil
   812  }
   813  
   814  func jsonDataToNomsValue(ctx context.Context, vrw types.ValueReadWriter, data []byte) (types.JSON, error) {
   815  	var doc interface{}
   816  	if err := json.Unmarshal(data, &doc); err != nil {
   817  		return types.JSON{}, err
   818  	}
   819  	sqlDoc := gmstypes.JSONDocument{Val: doc}
   820  	nomsJson, err := json2.NomsJSONFromJSONValue(ctx, vrw, sqlDoc)
   821  	if err != nil {
   822  		return types.JSON{}, err
   823  	}
   824  	return types.JSON(nomsJson), nil
   825  }