github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/merge/violations_fk_prolly.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 merge
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io"
    21  	"strings"
    22  
    23  	"github.com/dolthub/go-mysql-server/sql"
    24  
    25  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    28  	"github.com/dolthub/dolt/go/libraries/doltcore/table/editor/creation"
    29  	"github.com/dolthub/dolt/go/store/pool"
    30  	"github.com/dolthub/dolt/go/store/prolly"
    31  	"github.com/dolthub/dolt/go/store/prolly/tree"
    32  	"github.com/dolthub/dolt/go/store/val"
    33  )
    34  
    35  func prollyParentSecDiffFkConstraintViolations(
    36  	ctx context.Context,
    37  	foreignKey doltdb.ForeignKey,
    38  	postParent, postChild *constraintViolationsLoadedTable,
    39  	preParentSecIdx prolly.Map,
    40  	receiver FKViolationReceiver) error {
    41  
    42  	postParentRowData := durable.ProllyMapFromIndex(postParent.RowData)
    43  	postParentSecIdx := durable.ProllyMapFromIndex(postParent.IndexData)
    44  	childSecIdx := durable.ProllyMapFromIndex(postChild.IndexData)
    45  
    46  	parentSecKD, _ := postParentSecIdx.Descriptors()
    47  	parentPrefixKD := parentSecKD.PrefixDesc(len(foreignKey.TableColumns))
    48  	partialKB := val.NewTupleBuilder(parentPrefixKD)
    49  
    50  	childPriIdx := durable.ProllyMapFromIndex(postChild.RowData)
    51  	childPriKD, _ := childPriIdx.Descriptors()
    52  
    53  	var err error
    54  	// TODO: Determine whether we should surface every row as a diff when the map's value descriptor has changed.
    55  	considerAllRowsModified := false
    56  	err = prolly.DiffMaps(ctx, preParentSecIdx, postParentSecIdx, considerAllRowsModified, func(ctx context.Context, diff tree.Diff) error {
    57  		switch diff.Type {
    58  		case tree.RemovedDiff, tree.ModifiedDiff:
    59  			toSecKey, hadNulls := makePartialKey(partialKB, foreignKey.ReferencedTableColumns, postParent.Index, postParent.IndexSchema, val.Tuple(diff.Key), val.Tuple(diff.From), preParentSecIdx.Pool())
    60  			if hadNulls {
    61  				// row had some nulls previously, so it couldn't have been a parent
    62  				return nil
    63  			}
    64  
    65  			ok, err := postParentSecIdx.HasPrefix(ctx, toSecKey, parentPrefixKD)
    66  			if err != nil {
    67  				return err
    68  			}
    69  			if ok {
    70  				return nil
    71  			}
    72  
    73  			// All equivalent parents were deleted, let's check for dangling children.
    74  			// We search for matching keys in the child's secondary index
    75  			err = createCVsForPartialKeyMatches(ctx, toSecKey, parentPrefixKD, childPriKD, childPriIdx, childSecIdx, postParentRowData.Pool(), receiver)
    76  			if err != nil {
    77  				return err
    78  			}
    79  		case tree.AddedDiff:
    80  		default:
    81  			panic("unhandled diff type")
    82  		}
    83  		return nil
    84  	})
    85  	if err != nil && err != io.EOF {
    86  		return err
    87  	}
    88  
    89  	return nil
    90  }
    91  
    92  func prollyParentPriDiffFkConstraintViolations(
    93  	ctx context.Context,
    94  	foreignKey doltdb.ForeignKey,
    95  	postParent, postChild *constraintViolationsLoadedTable,
    96  	preParentRowData prolly.Map,
    97  	receiver FKViolationReceiver) error {
    98  	postParentRowData := durable.ProllyMapFromIndex(postParent.RowData)
    99  	postParentIndexData := durable.ProllyMapFromIndex(postParent.IndexData)
   100  
   101  	idxDesc, _ := postParentIndexData.Descriptors()
   102  	partialDesc := idxDesc.PrefixDesc(len(foreignKey.TableColumns))
   103  	partialKB := val.NewTupleBuilder(partialDesc)
   104  
   105  	childPriIdx := durable.ProllyMapFromIndex(postChild.RowData)
   106  	childScndryIdx := durable.ProllyMapFromIndex(postChild.IndexData)
   107  	primaryKD, _ := childPriIdx.Descriptors()
   108  
   109  	// TODO: Determine whether we should surface every row as a diff when the map's value descriptor has changed.
   110  	considerAllRowsModified := false
   111  	err := prolly.DiffMaps(ctx, preParentRowData, postParentRowData, considerAllRowsModified, func(ctx context.Context, diff tree.Diff) error {
   112  		switch diff.Type {
   113  		case tree.RemovedDiff, tree.ModifiedDiff:
   114  			partialKey, hadNulls := makePartialKey(partialKB, foreignKey.ReferencedTableColumns, postParent.Index, postParent.Schema, val.Tuple(diff.Key), val.Tuple(diff.From), preParentRowData.Pool())
   115  			if hadNulls {
   116  				// row had some nulls previously, so it couldn't have been a parent
   117  				return nil
   118  			}
   119  
   120  			partialKeyRange := prolly.PrefixRange(partialKey, partialDesc)
   121  			itr, err := postParentIndexData.IterRange(ctx, partialKeyRange)
   122  			if err != nil {
   123  				return err
   124  			}
   125  
   126  			_, _, err = itr.Next(ctx)
   127  			if err != nil && err != io.EOF {
   128  				return err
   129  			}
   130  			if err == nil {
   131  				// some other equivalent parents exist
   132  				return nil
   133  			}
   134  
   135  			// All equivalent parents were deleted, let's check for dangling children.
   136  			// We search for matching keys in the child's secondary index
   137  			err = createCVsForPartialKeyMatches(ctx, partialKey, partialDesc, primaryKD, childPriIdx, childScndryIdx, childPriIdx.Pool(), receiver)
   138  			if err != nil {
   139  				return err
   140  			}
   141  
   142  		case tree.AddedDiff:
   143  		default:
   144  			panic("unhandled diff type")
   145  		}
   146  
   147  		return nil
   148  	})
   149  	if err != nil && err != io.EOF {
   150  		return err
   151  	}
   152  
   153  	return nil
   154  }
   155  
   156  func prollyChildPriDiffFkConstraintViolations(
   157  	ctx context.Context,
   158  	foreignKey doltdb.ForeignKey,
   159  	postParent, postChild *constraintViolationsLoadedTable,
   160  	preChildRowData prolly.Map,
   161  	receiver FKViolationReceiver) error {
   162  	postChildRowData := durable.ProllyMapFromIndex(postChild.RowData)
   163  	parentScndryIdx := durable.ProllyMapFromIndex(postParent.IndexData)
   164  
   165  	idxDesc, _ := parentScndryIdx.Descriptors()
   166  	partialDesc := idxDesc.PrefixDesc(len(foreignKey.TableColumns))
   167  	partialKB := val.NewTupleBuilder(partialDesc)
   168  
   169  	// TODO: Determine whether we should surface every row as a diff when the map's value descriptor has changed.
   170  	considerAllRowsModified := false
   171  	err := prolly.DiffMaps(ctx, preChildRowData, postChildRowData, considerAllRowsModified, func(ctx context.Context, diff tree.Diff) error {
   172  		switch diff.Type {
   173  		case tree.AddedDiff, tree.ModifiedDiff:
   174  			k, v := val.Tuple(diff.Key), val.Tuple(diff.To)
   175  			partialKey, hasNulls := makePartialKey(
   176  				partialKB,
   177  				foreignKey.TableColumns,
   178  				postChild.Index,
   179  				postChild.Schema,
   180  				k,
   181  				v,
   182  				preChildRowData.Pool())
   183  			if hasNulls {
   184  				return nil
   185  			}
   186  
   187  			err := createCVIfNoPartialKeyMatchesPri(ctx, k, v, partialKey, partialDesc, parentScndryIdx, receiver)
   188  			if err != nil {
   189  				return err
   190  			}
   191  		case tree.RemovedDiff:
   192  		default:
   193  			panic("unhandled diff type")
   194  		}
   195  		return nil
   196  	})
   197  	if err != nil && err != io.EOF {
   198  		return err
   199  	}
   200  
   201  	return nil
   202  }
   203  
   204  func prollyChildSecDiffFkConstraintViolations(
   205  	ctx context.Context,
   206  	foreignKey doltdb.ForeignKey,
   207  	postParent, postChild *constraintViolationsLoadedTable,
   208  	preChildSecIdx prolly.Map,
   209  	receiver FKViolationReceiver) error {
   210  	postChildRowData := durable.ProllyMapFromIndex(postChild.RowData)
   211  	postChildSecIdx := durable.ProllyMapFromIndex(postChild.IndexData)
   212  	parentSecIdx := durable.ProllyMapFromIndex(postParent.IndexData)
   213  
   214  	parentSecIdxDesc, _ := parentSecIdx.Descriptors()
   215  	prefixDesc := parentSecIdxDesc.PrefixDesc(len(foreignKey.TableColumns))
   216  	childPriKD, _ := postChildRowData.Descriptors()
   217  	childPriKB := val.NewTupleBuilder(childPriKD)
   218  
   219  	// TODO: Determine whether we should surface every row as a diff when the map's value descriptor has changed.
   220  	considerAllRowsModified := false
   221  	err := prolly.DiffMaps(ctx, preChildSecIdx, postChildSecIdx, considerAllRowsModified, func(ctx context.Context, diff tree.Diff) error {
   222  		switch diff.Type {
   223  		case tree.AddedDiff, tree.ModifiedDiff:
   224  			k := val.Tuple(diff.Key)
   225  			// TODO: possible to skip this if there are not null constraints over entire index
   226  			for i := 0; i < k.Count(); i++ {
   227  				if k.FieldIsNull(i) {
   228  					return nil
   229  				}
   230  			}
   231  
   232  			ok, err := parentSecIdx.HasPrefix(ctx, k, prefixDesc)
   233  			if err != nil {
   234  				return err
   235  			} else if !ok {
   236  				return createCVForSecIdx(ctx, k, childPriKD, childPriKB, postChildRowData, postChildRowData.Pool(), receiver)
   237  			}
   238  			return nil
   239  
   240  		case tree.RemovedDiff:
   241  		default:
   242  			panic("unhandled diff type")
   243  		}
   244  		return nil
   245  	})
   246  	if err != nil && err != io.EOF {
   247  		return err
   248  	}
   249  	return nil
   250  }
   251  
   252  func createCVIfNoPartialKeyMatchesPri(
   253  	ctx context.Context,
   254  	k, v, partialKey val.Tuple,
   255  	partialKeyDesc val.TupleDesc,
   256  	idx prolly.Map,
   257  	receiver FKViolationReceiver) error {
   258  	itr, err := creation.NewPrefixItr(ctx, partialKey, partialKeyDesc, idx)
   259  	if err != nil {
   260  		return err
   261  	}
   262  	_, _, err = itr.Next(ctx)
   263  	if err != nil && err != io.EOF {
   264  		return err
   265  	}
   266  	if err == nil {
   267  		return nil
   268  	}
   269  
   270  	return receiver.ProllyFKViolationFound(ctx, k, v)
   271  }
   272  
   273  func createCVForSecIdx(
   274  	ctx context.Context,
   275  	k val.Tuple,
   276  	primaryKD val.TupleDesc,
   277  	primaryKb *val.TupleBuilder,
   278  	pri prolly.Map,
   279  	pool pool.BuffPool,
   280  	receiver FKViolationReceiver) error {
   281  
   282  	// convert secondary idx entry to primary row key
   283  	// the pks of the table are the last keys of the index
   284  	o := k.Count() - primaryKD.Count()
   285  	for i := 0; i < primaryKD.Count(); i++ {
   286  		j := o + i
   287  		primaryKb.PutRaw(i, k.GetField(j))
   288  	}
   289  	primaryIdxKey := primaryKb.Build(pool)
   290  
   291  	var value val.Tuple
   292  	err := pri.Get(ctx, primaryIdxKey, func(k, v val.Tuple) error {
   293  		value = v
   294  		return nil
   295  	})
   296  	if err != nil {
   297  		return err
   298  	}
   299  
   300  	return receiver.ProllyFKViolationFound(ctx, primaryIdxKey, value)
   301  }
   302  
   303  func createCVsForPartialKeyMatches(
   304  	ctx context.Context,
   305  	partialKey val.Tuple,
   306  	partialKeyDesc val.TupleDesc,
   307  	primaryKD val.TupleDesc,
   308  	primaryIdx prolly.Map,
   309  	secondaryIdx prolly.Map,
   310  	pool pool.BuffPool,
   311  	receiver FKViolationReceiver,
   312  ) error {
   313  
   314  	itr, err := creation.NewPrefixItr(ctx, partialKey, partialKeyDesc, secondaryIdx)
   315  	if err != nil {
   316  		return err
   317  	}
   318  
   319  	kb := val.NewTupleBuilder(primaryKD)
   320  
   321  	for k, _, err := itr.Next(ctx); err == nil; k, _, err = itr.Next(ctx) {
   322  
   323  		// convert secondary idx entry to primary row key
   324  		// the pks of the table are the last keys of the index
   325  		o := k.Count() - primaryKD.Count()
   326  		for i := 0; i < primaryKD.Count(); i++ {
   327  			j := o + i
   328  			kb.PutRaw(i, k.GetField(j))
   329  		}
   330  		primaryIdxKey := kb.Build(pool)
   331  
   332  		var value val.Tuple
   333  		err := primaryIdx.Get(ctx, primaryIdxKey, func(k, v val.Tuple) error {
   334  			value = v
   335  			return nil
   336  		})
   337  		if err != nil {
   338  			return err
   339  		}
   340  
   341  		err = receiver.ProllyFKViolationFound(ctx, primaryIdxKey, value)
   342  		if err != nil {
   343  			return err
   344  		}
   345  	}
   346  	if err != nil && err != io.EOF {
   347  		return err
   348  	}
   349  
   350  	return nil
   351  }
   352  
   353  func makePartialKey(kb *val.TupleBuilder, tags []uint64, idxSch schema.Index, tblSch schema.Schema, k, v val.Tuple, pool pool.BuffPool) (val.Tuple, bool) {
   354  	// Possible that the parent index (idxSch) is longer than the partial key (tags).
   355  	if idxSch.Name() != "" && len(idxSch.IndexedColumnTags()) <= len(tags) {
   356  		tags = idxSch.IndexedColumnTags()
   357  	}
   358  	for i, tag := range tags {
   359  		if j, ok := tblSch.GetPKCols().TagToIdx[tag]; ok {
   360  			if k.FieldIsNull(j) {
   361  				return nil, true
   362  			}
   363  			kb.PutRaw(i, k.GetField(j))
   364  			continue
   365  		}
   366  
   367  		j, _ := tblSch.GetNonPKCols().TagToIdx[tag]
   368  		if v.FieldIsNull(j) {
   369  			return nil, true
   370  		}
   371  		if schema.IsKeyless(tblSch) {
   372  			kb.PutRaw(i, v.GetField(j+1))
   373  		} else {
   374  			kb.PutRaw(i, v.GetField(j))
   375  		}
   376  	}
   377  
   378  	return kb.Build(pool), false
   379  }
   380  
   381  // TODO: Change json.NomsJson string marshalling to match json.Marshall
   382  // Currently it returns additional whitespace. Another option is to implement a
   383  // custom json encoder that matches json.NomsJson string marshalling.
   384  
   385  type FkCVMeta struct {
   386  	Columns           []string `json:"Columns"`
   387  	ForeignKey        string   `json:"ForeignKey"`
   388  	Index             string   `json:"Index"`
   389  	OnDelete          string   `json:"OnDelete"`
   390  	OnUpdate          string   `json:"OnUpdate"`
   391  	ReferencedColumns []string `json:"ReferencedColumns"`
   392  	ReferencedIndex   string   `json:"ReferencedIndex"`
   393  	ReferencedTable   string   `json:"ReferencedTable"`
   394  	Table             string   `json:"Table"`
   395  }
   396  
   397  var _ sql.JSONWrapper = FkCVMeta{}
   398  
   399  func (m FkCVMeta) ToInterface() (interface{}, error) {
   400  	return map[string]interface{}{
   401  		"Columns":           m.Columns,
   402  		"ForeignKey":        m.ForeignKey,
   403  		"Index":             m.Index,
   404  		"OnDelete":          m.OnDelete,
   405  		"OnUpdate":          m.OnUpdate,
   406  		"ReferencedColumns": m.ReferencedColumns,
   407  		"ReferencedIndex":   m.ReferencedIndex,
   408  		"ReferencedTable":   m.ReferencedTable,
   409  		"Table":             m.Table,
   410  	}, nil
   411  }
   412  
   413  // PrettyPrint is a custom pretty print function to match the old format's
   414  // output which includes additional whitespace between keys, values, and array elements.
   415  func (m FkCVMeta) PrettyPrint() string {
   416  	jsonStr := fmt.Sprintf(`{`+
   417  		`"Index": "%s", `+
   418  		`"Table": "%s", `+
   419  		`"Columns": ["%s"], `+
   420  		`"OnDelete": "%s", `+
   421  		`"OnUpdate": "%s", `+
   422  		`"ForeignKey": "%s", `+
   423  		`"ReferencedIndex": "%s", `+
   424  		`"ReferencedTable": "%s", `+
   425  		`"ReferencedColumns": ["%s"]}`,
   426  		m.Index,
   427  		m.Table,
   428  		strings.Join(m.Columns, `', '`),
   429  		m.OnDelete,
   430  		m.OnUpdate,
   431  		m.ForeignKey,
   432  		m.ReferencedIndex,
   433  		m.ReferencedTable,
   434  		strings.Join(m.ReferencedColumns, `', '`))
   435  	return jsonStr
   436  }