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 }