github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/diff/schema_diff.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 diff
    16  
    17  import (
    18  	"reflect"
    19  
    20  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    21  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    22  )
    23  
    24  type SchemaChangeType int
    25  
    26  const (
    27  	// SchDiffNone is the SchemaChangeType for two columns with the same tag that are identical
    28  	SchDiffNone SchemaChangeType = iota
    29  	// SchDiffAdded is the SchemaChangeType when a column is in the new schema but not the old
    30  	SchDiffAdded
    31  	// SchDiffRemoved is the SchemaChangeType when a column is in the old schema but not the new
    32  	SchDiffRemoved
    33  	// SchDiffModified is the SchemaChangeType for two columns with the same tag that are different
    34  	SchDiffModified
    35  )
    36  
    37  // ColumnDifference is the result of comparing two columns from two schemas.
    38  type ColumnDifference struct {
    39  	DiffType SchemaChangeType
    40  	Tag      uint64
    41  	Old      *schema.Column
    42  	New      *schema.Column
    43  }
    44  
    45  type columnPair [2]*schema.Column
    46  
    47  // DiffSchColumns compares two schemas by looking at columns with the same tag.
    48  func DiffSchColumns(fromSch, toSch schema.Schema) (map[uint64]ColumnDifference, []uint64) {
    49  	colPairMap, unionTags := pairColumns(fromSch, toSch)
    50  
    51  	diffs := make(map[uint64]ColumnDifference)
    52  	for _, tag := range unionTags {
    53  		colPair := colPairMap[tag]
    54  		if colPair[0] == nil {
    55  			diffs[tag] = ColumnDifference{SchDiffAdded, tag, nil, colPair[1]}
    56  		} else if colPair[1] == nil {
    57  			diffs[tag] = ColumnDifference{SchDiffRemoved, tag, colPair[0], nil}
    58  		} else if !colPair[0].Equals(*colPair[1]) {
    59  			diffs[tag] = ColumnDifference{SchDiffModified, tag, colPair[0], colPair[1]}
    60  		} else {
    61  			diffs[tag] = ColumnDifference{SchDiffNone, tag, colPair[0], colPair[1]}
    62  		}
    63  	}
    64  
    65  	return diffs, unionTags
    66  }
    67  
    68  // pairColumns loops over both sets of columns pairing columns with the same tag.
    69  func pairColumns(fromSch, toSch schema.Schema) (map[uint64]columnPair, []uint64) {
    70  	// collect the tag union of the two schemas, ordering fromSch before toSch
    71  	var unionTags []uint64
    72  	colPairMap := make(map[uint64]columnPair)
    73  
    74  	_ = fromSch.GetAllCols().Iter(func(tag uint64, col schema.Column) (stop bool, err error) {
    75  		colPairMap[tag] = columnPair{&col, nil}
    76  		unionTags = append(unionTags, tag)
    77  
    78  		return false, nil
    79  	})
    80  
    81  	_ = toSch.GetAllCols().Iter(func(tag uint64, col schema.Column) (stop bool, err error) {
    82  		if pair, ok := colPairMap[tag]; ok {
    83  			pair[1] = &col
    84  			colPairMap[tag] = pair
    85  		} else {
    86  			colPairMap[tag] = columnPair{nil, &col}
    87  			unionTags = append(unionTags, tag)
    88  		}
    89  
    90  		return false, nil
    91  	})
    92  
    93  	return colPairMap, unionTags
    94  }
    95  
    96  type IndexDifference struct {
    97  	DiffType SchemaChangeType
    98  	From     schema.Index
    99  	To       schema.Index
   100  }
   101  
   102  // DiffSchIndexes matches two sets of Indexes based on column definitions.
   103  // It returns matched and unmatched Indexes as a slice of IndexDifferences.
   104  func DiffSchIndexes(fromSch, toSch schema.Schema) (diffs []IndexDifference) {
   105  	_ = fromSch.Indexes().Iter(func(fromIdx schema.Index) (stop bool, err error) {
   106  		toIdx, ok := toSch.Indexes().GetIndexByTags(fromIdx.IndexedColumnTags()...)
   107  
   108  		if !ok {
   109  			diffs = append(diffs, IndexDifference{
   110  				DiffType: SchDiffRemoved,
   111  				From:     fromIdx,
   112  			})
   113  			return false, nil
   114  		}
   115  
   116  		d := IndexDifference{
   117  			DiffType: SchDiffModified,
   118  			From:     fromIdx,
   119  			To:       toIdx,
   120  		}
   121  
   122  		if fromIdx.Equals(toIdx) {
   123  			d.DiffType = SchDiffNone
   124  		}
   125  		diffs = append(diffs, d)
   126  
   127  		return false, nil
   128  	})
   129  
   130  	_ = toSch.Indexes().Iter(func(toIdx schema.Index) (stop bool, err error) {
   131  		// if we've seen this index, skip
   132  		for _, d := range diffs {
   133  			if d.To != nil && d.To.Equals(toIdx) {
   134  				return false, nil
   135  			}
   136  		}
   137  
   138  		diffs = append(diffs, IndexDifference{
   139  			DiffType: SchDiffAdded,
   140  			To:       toIdx,
   141  		})
   142  
   143  		return false, nil
   144  	})
   145  
   146  	return diffs
   147  }
   148  
   149  type ForeignKeyDifference struct {
   150  	DiffType SchemaChangeType
   151  	From     doltdb.ForeignKey
   152  	To       doltdb.ForeignKey
   153  }
   154  
   155  // DiffForeignKeys matches two sets of ForeignKeys based on column definitions.
   156  // It returns matched and unmatched ForeignKeys as a slice of ForeignKeyDifferences.
   157  func DiffForeignKeys(fromFks, toFKs []doltdb.ForeignKey) (diffs []ForeignKeyDifference) {
   158  	for _, from := range fromFks {
   159  		matched := false
   160  		for _, to := range toFKs {
   161  			if reflect.DeepEqual(from.ReferencedTableColumns, to.ReferencedTableColumns) &&
   162  				reflect.DeepEqual(from.TableColumns, to.TableColumns) {
   163  
   164  				matched = true
   165  				d := ForeignKeyDifference{
   166  					DiffType: SchDiffModified,
   167  					From:     from,
   168  					To:       to,
   169  				}
   170  
   171  				if from.DeepEquals(to) {
   172  					d.DiffType = SchDiffNone
   173  				}
   174  				diffs = append(diffs, d)
   175  
   176  				break
   177  			}
   178  		}
   179  
   180  		if !matched {
   181  			diffs = append(diffs, ForeignKeyDifference{
   182  				DiffType: SchDiffRemoved,
   183  				From:     from,
   184  			})
   185  		}
   186  	}
   187  
   188  	for _, to := range toFKs {
   189  		seen := false
   190  		for _, d := range diffs {
   191  			if d.To.DeepEquals(to) {
   192  				seen = true
   193  				break
   194  			}
   195  		}
   196  		if seen {
   197  			continue
   198  		}
   199  
   200  		diffs = append(diffs, ForeignKeyDifference{
   201  			DiffType: SchDiffAdded,
   202  			To:       to,
   203  		})
   204  	}
   205  	return diffs
   206  }