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

     1  // Copyright 2019 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  	"errors"
    20  	"io"
    21  
    22  	"github.com/dolthub/dolt/go/libraries/doltcore/conflict"
    23  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable"
    24  
    25  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/row"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/rowconv"
    28  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    29  	"github.com/dolthub/dolt/go/store/types"
    30  )
    31  
    32  const (
    33  	oursStr   = "our"
    34  	theirsStr = "their"
    35  	baseStr   = "base"
    36  )
    37  
    38  const (
    39  	ConflictDiffTypeAdded    = "added"
    40  	ConflictDiffTypeModified = "modified"
    41  	ConflictDiffTypeRemoved  = "removed"
    42  )
    43  
    44  // ConflictReader is a class providing a NextConflict function which can be used in a pipeline as a pipeline.SourceFunc,
    45  // or it can be used to read each conflict
    46  type ConflictReader struct {
    47  	confItr types.MapIterator
    48  	joiner  *rowconv.Joiner
    49  	sch     schema.Schema
    50  	nbf     *types.NomsBinFormat
    51  	keyless bool
    52  }
    53  
    54  // NewConflictReader returns a new conflict reader for a given table
    55  func NewConflictReader(ctx context.Context, tbl *doltdb.Table, tblName string) (*ConflictReader, error) {
    56  	base, sch, mergeSch, err := tbl.GetConflictSchemas(ctx, tblName) // tblName unused by old storage format
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  	if base == nil || sch == nil || mergeSch == nil {
    61  		base, err = tbl.GetSchema(ctx)
    62  		sch, mergeSch = base, base
    63  	}
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	joiner, err := rowconv.NewJoiner(
    69  		[]rowconv.NamedSchema{
    70  			{Name: baseStr, Sch: base},
    71  			{Name: oursStr, Sch: sch},
    72  			{Name: theirsStr, Sch: mergeSch},
    73  		}, map[string]rowconv.ColNamingFunc{
    74  			baseStr:   func(colName string) string { return baseStr + "_" + colName },
    75  			oursStr:   func(colName string) string { return oursStr + "_" + colName },
    76  			theirsStr: func(colName string) string { return theirsStr + "_" + colName },
    77  		},
    78  	)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	readerSch := joiner.GetSchema()
    83  	readerSch, err = readerSch.AddColumn(schema.NewColumn("our_diff_type", schema.DoltConflictsOurDiffTypeTag, types.StringKind, false), nil)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	readerSch, err = readerSch.AddColumn(schema.NewColumn("their_diff_type", schema.DoltConflictsTheirDiffTypeTag, types.StringKind, false), nil)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	var keyless bool
    93  	if schema.IsKeyless(sch) {
    94  		keyless = true
    95  		readerSch, err = readerSch.AddColumn(
    96  			schema.NewColumn(
    97  				"base_cardinality",
    98  				schema.DoltConflictsBaseCardinalityTag,
    99  				types.UintKind,
   100  				false),
   101  			nil)
   102  		if err != nil {
   103  			return nil, err
   104  		}
   105  		readerSch, err = readerSch.AddColumn(
   106  			schema.NewColumn(
   107  				"our_cardinality",
   108  				schema.DoltConflictsOurCardinalityTag,
   109  				types.UintKind,
   110  				false),
   111  			nil)
   112  		if err != nil {
   113  			return nil, err
   114  		}
   115  		readerSch, err = readerSch.AddColumn(
   116  			schema.NewColumn(
   117  				"their_cardinality",
   118  				schema.DoltConflictsTheirCardinalityTag,
   119  				types.UintKind,
   120  				false),
   121  			nil)
   122  		if err != nil {
   123  			return nil, err
   124  		}
   125  	}
   126  
   127  	_, confIdx, err := tbl.GetConflicts(ctx)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	if confIdx.Format() == types.Format_DOLT {
   133  		panic("conflict reader not implemented for new storage format")
   134  	}
   135  
   136  	confData := durable.NomsMapFromConflictIndex(confIdx)
   137  	confItr, err := confData.Iterator(ctx)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	return &ConflictReader{
   143  		confItr: confItr,
   144  		joiner:  joiner,
   145  		sch:     readerSch,
   146  		nbf:     tbl.Format(),
   147  		keyless: keyless,
   148  	}, nil
   149  }
   150  
   151  // GetSchema gets the schema of the rows that this reader will return
   152  func (cr *ConflictReader) GetSchema() schema.Schema {
   153  	return cr.sch
   154  }
   155  
   156  // GetJoiner returns the joiner used to join a row with its base, and merge versions
   157  func (cr *ConflictReader) GetJoiner() *rowconv.Joiner {
   158  	return cr.joiner
   159  }
   160  
   161  // NextConflict can be called successively to retrieve the conflicts in a table.  Once all conflicts have been returned
   162  // io.EOF will be returned in the error field.  This can be used in a pipeline, or to iterate through all the conflicts
   163  // in a table.
   164  func (cr *ConflictReader) NextConflict(ctx context.Context) (row.Row, error) {
   165  	key, value, err := cr.confItr.Next(ctx)
   166  
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	if key == nil {
   172  		return nil, io.EOF
   173  	}
   174  
   175  	keyTpl := key.(types.Tuple)
   176  	conflict, err := conflict.ConflictFromTuple(value.(types.Tuple))
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  
   181  	var joinedRow row.Row
   182  	if !cr.keyless {
   183  		joinedRow, err = cr.pkJoinedRow(keyTpl, conflict)
   184  	} else {
   185  		joinedRow, err = cr.keylessJoinedRow(keyTpl, conflict)
   186  	}
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	ourDiffType := getDiffType(conflict.Base, conflict.Value)
   192  	theirDiffType := getDiffType(conflict.Base, conflict.MergeValue)
   193  	joinedRow, err = joinedRow.SetColVal(schema.DoltConflictsOurDiffTypeTag, types.String(ourDiffType), cr.sch)
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  	joinedRow, err = joinedRow.SetColVal(schema.DoltConflictsTheirDiffTypeTag, types.String(theirDiffType), cr.sch)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	return joinedRow, nil
   203  }
   204  
   205  func (cr *ConflictReader) pkJoinedRow(key types.Tuple, conflict conflict.Conflict) (row.Row, error) {
   206  	var err error
   207  	namedRows := make(map[string]row.Row)
   208  
   209  	if !types.IsNull(conflict.Base) {
   210  		namedRows[baseStr], err = row.FromNoms(cr.joiner.SchemaForName(baseStr), key, conflict.Base.(types.Tuple))
   211  		if err != nil {
   212  			return nil, err
   213  		}
   214  	}
   215  	if !types.IsNull(conflict.Value) {
   216  		namedRows[oursStr], err = row.FromNoms(cr.joiner.SchemaForName(oursStr), key, conflict.Value.(types.Tuple))
   217  		if err != nil {
   218  			return nil, err
   219  		}
   220  	}
   221  	if !types.IsNull(conflict.MergeValue) {
   222  		namedRows[theirsStr], err = row.FromNoms(cr.joiner.SchemaForName(theirsStr), key, conflict.MergeValue.(types.Tuple))
   223  		if err != nil {
   224  			return nil, err
   225  		}
   226  	}
   227  
   228  	joinedRow, err := cr.joiner.Join(namedRows)
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	return joinedRow, nil
   234  }
   235  
   236  func (cr *ConflictReader) keylessJoinedRow(key types.Tuple, conflict conflict.Conflict) (row.Row, error) {
   237  	var err error
   238  	namedRows := make(map[string]row.Row)
   239  	var baseCard, ourCard, theirCard uint64
   240  
   241  	if !types.IsNull(conflict.Base) {
   242  		namedRows[baseStr], baseCard, err = row.KeylessRowsFromTuples(key, conflict.Base.(types.Tuple))
   243  		if err != nil {
   244  			return nil, err
   245  		}
   246  	}
   247  	if !types.IsNull(conflict.Value) {
   248  		namedRows[oursStr], ourCard, err = row.KeylessRowsFromTuples(key, conflict.Value.(types.Tuple))
   249  		if err != nil {
   250  			return nil, err
   251  		}
   252  	}
   253  	if !types.IsNull(conflict.MergeValue) {
   254  		namedRows[theirsStr], theirCard, err = row.KeylessRowsFromTuples(key, conflict.MergeValue.(types.Tuple))
   255  		if err != nil {
   256  			return nil, err
   257  		}
   258  	}
   259  
   260  	joinedRow, err := cr.joiner.Join(namedRows)
   261  	if err != nil {
   262  		return nil, err
   263  	}
   264  	joinedRow, err = setCardinalities(types.Uint(baseCard), types.Uint(ourCard), types.Uint(theirCard), joinedRow, cr.sch)
   265  	if err != nil {
   266  		return nil, err
   267  	}
   268  
   269  	return joinedRow, nil
   270  }
   271  
   272  func setCardinalities(base, ours, theirs types.Uint, joinedRow row.Row, sch schema.Schema) (row.Row, error) {
   273  	joinedRow, err := joinedRow.SetColVal(schema.DoltConflictsBaseCardinalityTag, base, sch)
   274  	if err != nil {
   275  		return nil, err
   276  	}
   277  	joinedRow, err = joinedRow.SetColVal(schema.DoltConflictsOurCardinalityTag, ours, sch)
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  	joinedRow, err = joinedRow.SetColVal(schema.DoltConflictsTheirCardinalityTag, theirs, sch)
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  	return joinedRow, nil
   286  }
   287  
   288  func getDiffType(base types.Value, other types.Value) string {
   289  	if types.IsNull(base) {
   290  		return ConflictDiffTypeAdded
   291  	} else if types.IsNull(other) {
   292  		return ConflictDiffTypeRemoved
   293  	}
   294  
   295  	return ConflictDiffTypeModified
   296  }
   297  
   298  // GetKeyForConflicts returns the pk for a conflict row
   299  func (cr *ConflictReader) GetKeyForConflict(ctx context.Context, r row.Row) (types.Value, error) {
   300  	rows, err := cr.joiner.Split(r)
   301  
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  
   306  	for rowType, r := range rows {
   307  		key, err := r.NomsMapKey(cr.joiner.SchemaForName(rowType)).Value(ctx)
   308  
   309  		if err != nil {
   310  			return nil, err
   311  		}
   312  
   313  		return key, nil
   314  	}
   315  
   316  	return nil, errors.New("could not determine key")
   317  }
   318  
   319  // Close should release resources being held
   320  func (cr *ConflictReader) Close() error {
   321  	return nil
   322  }