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 }