github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/diff/table_deltas.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  	"context"
    19  	"fmt"
    20  	"sort"
    21  
    22  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdocs"
    23  
    24  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    25  	"github.com/dolthub/dolt/go/libraries/doltcore/env"
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    27  	"github.com/dolthub/dolt/go/libraries/utils/set"
    28  	"github.com/dolthub/dolt/go/store/hash"
    29  	"github.com/dolthub/dolt/go/store/types"
    30  )
    31  
    32  type TableDiffType int
    33  
    34  const (
    35  	AddedTable TableDiffType = iota
    36  	ModifiedTable
    37  	RenamedTable
    38  	RemovedTable
    39  )
    40  
    41  type DocDiffType int
    42  
    43  const (
    44  	AddedDoc DocDiffType = iota
    45  	ModifiedDoc
    46  	RemovedDoc
    47  )
    48  
    49  type DocDiffs struct {
    50  	NumAdded    int
    51  	NumModified int
    52  	NumRemoved  int
    53  	DocToType   map[string]DocDiffType
    54  	Docs        []string
    55  }
    56  
    57  // NewDocDiffs returns DocDiffs for Dolt Docs between two roots.
    58  func NewDocDiffs(ctx context.Context, older *doltdb.RootValue, newer *doltdb.RootValue, docs doltdocs.Docs) (*DocDiffs, error) {
    59  	var added []string
    60  	var modified []string
    61  	var removed []string
    62  	if older != nil {
    63  		if newer == nil {
    64  			a, m, r, err := DocsDiff(ctx, older, nil, docs)
    65  			if err != nil {
    66  				return nil, err
    67  			}
    68  			added = a
    69  			modified = m
    70  			removed = r
    71  		} else {
    72  			a, m, r, err := DocsDiff(ctx, older, newer, docs)
    73  			if err != nil {
    74  				return nil, err
    75  			}
    76  			added = a
    77  			modified = m
    78  			removed = r
    79  		}
    80  	}
    81  	var docNames []string
    82  	docNames = append(docNames, added...)
    83  	docNames = append(docNames, modified...)
    84  	docNames = append(docNames, removed...)
    85  	sort.Strings(docNames)
    86  
    87  	docsToType := make(map[string]DocDiffType)
    88  	for _, nt := range added {
    89  		docsToType[nt] = AddedDoc
    90  	}
    91  
    92  	for _, nt := range modified {
    93  		docsToType[nt] = ModifiedDoc
    94  	}
    95  
    96  	for _, nt := range removed {
    97  		docsToType[nt] = RemovedDoc
    98  	}
    99  
   100  	return &DocDiffs{len(added), len(modified), len(removed), docsToType, docNames}, nil
   101  }
   102  
   103  // Len returns the number of docs in a DocDiffs
   104  func (nd *DocDiffs) Len() int {
   105  	return len(nd.Docs)
   106  }
   107  
   108  // GetDocDiffs retrieves staged and unstaged DocDiffs.
   109  func GetDocDiffs(ctx context.Context, ddb *doltdb.DoltDB, rsr env.RepoStateReader, drw env.DocsReadWriter) (*DocDiffs, *DocDiffs, error) {
   110  	docsOnDisk, err := drw.GetDocsOnDisk()
   111  	if err != nil {
   112  		return nil, nil, err
   113  	}
   114  
   115  	workingRoot, err := env.WorkingRoot(ctx, ddb, rsr)
   116  	if err != nil {
   117  		return nil, nil, err
   118  	}
   119  
   120  	notStagedDocDiffs, err := NewDocDiffs(ctx, workingRoot, nil, docsOnDisk)
   121  	if err != nil {
   122  		return nil, nil, err
   123  	}
   124  
   125  	headRoot, err := env.HeadRoot(ctx, ddb, rsr)
   126  	if err != nil {
   127  		return nil, nil, err
   128  	}
   129  
   130  	stagedRoot, err := env.StagedRoot(ctx, ddb, rsr)
   131  	if err != nil {
   132  		return nil, nil, err
   133  	}
   134  
   135  	stagedDocDiffs, err := NewDocDiffs(ctx, headRoot, stagedRoot, docsOnDisk)
   136  	if err != nil {
   137  		return nil, nil, err
   138  	}
   139  
   140  	return stagedDocDiffs, notStagedDocDiffs, nil
   141  }
   142  
   143  // TableDelta represents the change of a single table between two roots.
   144  // FromFKs and ToFKs contain Foreign Keys that constrain columns in this table,
   145  // they do not contain Foreign Keys that reference this table.
   146  type TableDelta struct {
   147  	FromName       string
   148  	ToName         string
   149  	FromTable      *doltdb.Table
   150  	ToTable        *doltdb.Table
   151  	FromFks        []doltdb.ForeignKey
   152  	ToFks          []doltdb.ForeignKey
   153  	ToFksParentSch map[string]schema.Schema
   154  }
   155  
   156  // GetTableDeltas returns a slice of TableDelta objects for each table that changed between fromRoot and toRoot.
   157  // It matches tables across roots using the tag of the first primary key column in the table's schema.
   158  func GetTableDeltas(ctx context.Context, fromRoot, toRoot *doltdb.RootValue) (deltas []TableDelta, err error) {
   159  	deltas, err = getKeylessDeltas(ctx, fromRoot, toRoot)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	fromTables := make(map[uint64]*doltdb.Table)
   165  	fromTableNames := make(map[uint64]string)
   166  	fromTableFKs := make(map[uint64][]doltdb.ForeignKey)
   167  	fromTableHashes := make(map[uint64]hash.Hash)
   168  
   169  	fromFKC, err := fromRoot.GetForeignKeyCollection(ctx)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	err = fromRoot.IterTables(ctx, func(name string, table *doltdb.Table, sch schema.Schema) (stop bool, err error) {
   175  		if schema.IsKeyless(sch) {
   176  			return
   177  		}
   178  
   179  		th, err := table.HashOf()
   180  		if err != nil {
   181  			return true, err
   182  		}
   183  
   184  		pkTag := getUniqueTag(sch)
   185  		fromTables[pkTag] = table
   186  		fromTableNames[pkTag] = name
   187  		fromTableHashes[pkTag] = th
   188  		fromTableFKs[pkTag], _ = fromFKC.KeysForTable(name)
   189  		return false, nil
   190  	})
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	toFKC, err := toRoot.GetForeignKeyCollection(ctx)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  
   200  	err = toRoot.IterTables(ctx, func(name string, table *doltdb.Table, sch schema.Schema) (stop bool, err error) {
   201  		if schema.IsKeyless(sch) {
   202  			return
   203  		}
   204  
   205  		th, err := table.HashOf()
   206  		if err != nil {
   207  			return true, err
   208  		}
   209  
   210  		toFKs, _ := toFKC.KeysForTable(name)
   211  		toFksParentSch, err := getFkParentSchs(ctx, toRoot, toFKs...)
   212  		if err != nil {
   213  			return false, err
   214  		}
   215  
   216  		pkTag := getUniqueTag(sch)
   217  		oldName, ok := fromTableNames[pkTag]
   218  
   219  		if !ok {
   220  			deltas = append(deltas, TableDelta{
   221  				ToName:         name,
   222  				ToTable:        table,
   223  				ToFks:          toFKs,
   224  				ToFksParentSch: toFksParentSch,
   225  			})
   226  		} else if oldName != name ||
   227  			fromTableHashes[pkTag] != th ||
   228  			!fkSlicesAreEqual(fromTableFKs[pkTag], toFKs) {
   229  
   230  			deltas = append(deltas, TableDelta{
   231  				FromName:       fromTableNames[pkTag],
   232  				ToName:         name,
   233  				FromTable:      fromTables[pkTag],
   234  				ToTable:        table,
   235  				FromFks:        fromTableFKs[pkTag],
   236  				ToFks:          toFKs,
   237  				ToFksParentSch: toFksParentSch,
   238  			})
   239  		}
   240  
   241  		if ok {
   242  			delete(fromTableNames, pkTag) // consume table name
   243  		}
   244  
   245  		return false, nil
   246  	})
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  
   251  	// all unmatched tables in fromRoot must have been dropped
   252  	for pkTag, oldName := range fromTableNames {
   253  		deltas = append(deltas, TableDelta{
   254  			FromName:  oldName,
   255  			FromTable: fromTables[pkTag],
   256  			FromFks:   fromTableFKs[pkTag],
   257  		})
   258  	}
   259  
   260  	return deltas, nil
   261  }
   262  
   263  func GetStagedUnstagedTableDeltas(ctx context.Context, ddb *doltdb.DoltDB, rsr env.RepoStateReader) (staged, unstaged []TableDelta, err error) {
   264  	headRoot, err := env.HeadRoot(ctx, ddb, rsr)
   265  	if err != nil {
   266  		return nil, nil, doltdb.RootValueUnreadable{RootType: doltdb.HeadRoot, Cause: err}
   267  	}
   268  
   269  	stagedRoot, err := env.StagedRoot(ctx, ddb, rsr)
   270  	if err != nil {
   271  		return nil, nil, doltdb.RootValueUnreadable{RootType: doltdb.StagedRoot, Cause: err}
   272  	}
   273  
   274  	workingRoot, err := env.WorkingRoot(ctx, ddb, rsr)
   275  	if err != nil {
   276  		return nil, nil, doltdb.RootValueUnreadable{RootType: doltdb.WorkingRoot, Cause: err}
   277  	}
   278  
   279  	staged, err = GetTableDeltas(ctx, headRoot, stagedRoot)
   280  	if err != nil {
   281  		return nil, nil, err
   282  	}
   283  
   284  	unstaged, err = GetTableDeltas(ctx, stagedRoot, workingRoot)
   285  	if err != nil {
   286  		return nil, nil, err
   287  	}
   288  
   289  	return staged, unstaged, nil
   290  }
   291  
   292  // we don't have any stable identifier to a keyless table, have to do an n^2 match
   293  // todo: this is a good reason to implement table tags
   294  func getKeylessDeltas(ctx context.Context, fromRoot, toRoot *doltdb.RootValue) (deltas []TableDelta, err error) {
   295  	type fromTable struct {
   296  		tags *set.Uint64Set
   297  		tbl  *doltdb.Table
   298  		hsh  hash.Hash
   299  	}
   300  
   301  	fromTables := make(map[string]fromTable)
   302  	err = fromRoot.IterTables(ctx, func(name string, tbl *doltdb.Table, sch schema.Schema) (stop bool, err error) {
   303  		if !schema.IsKeyless(sch) {
   304  			return
   305  		}
   306  
   307  		h, err := tbl.HashOf()
   308  		if err != nil {
   309  			return false, err
   310  		}
   311  
   312  		fromTables[name] = fromTable{
   313  			tags: set.NewUint64Set(sch.GetAllCols().Tags),
   314  			tbl:  tbl,
   315  			hsh:  h,
   316  		}
   317  		return
   318  	})
   319  	if err != nil {
   320  		return nil, err
   321  	}
   322  
   323  	err = toRoot.IterTables(ctx, func(name string, tbl *doltdb.Table, sch schema.Schema) (stop bool, err error) {
   324  		if !schema.IsKeyless(sch) {
   325  			return
   326  		}
   327  
   328  		toTblHash, err := tbl.HashOf()
   329  		if err != nil {
   330  			return false, err
   331  		}
   332  
   333  		delta := TableDelta{
   334  			ToName:  name,
   335  			ToTable: tbl,
   336  		}
   337  
   338  		toTableTags := set.NewUint64Set(sch.GetAllCols().Tags)
   339  		for fromName, fromTbl := range fromTables {
   340  
   341  			// |tbl| and |fromTbl| have the same identity
   342  			// if they have column tags in common
   343  			if toTableTags.Intersection(fromTbl.tags).Size() > 0 {
   344  
   345  				// consume matched fromTable
   346  				delete(fromTables, fromName)
   347  
   348  				if toTblHash.Equal(fromTbl.hsh) {
   349  					// no diff, skip table
   350  					return
   351  				}
   352  
   353  				delta.FromName = fromName
   354  				delta.FromTable = fromTbl.tbl
   355  				break
   356  			}
   357  		}
   358  
   359  		// append if matched or unmatched
   360  		deltas = append(deltas, delta)
   361  		return
   362  	})
   363  	if err != nil {
   364  		return nil, err
   365  	}
   366  
   367  	// all unmatched pairs are table drops
   368  	for name, fromPair := range fromTables {
   369  		deltas = append(deltas, TableDelta{
   370  			FromName:  name,
   371  			FromTable: fromPair.tbl,
   372  		})
   373  	}
   374  
   375  	return deltas, nil
   376  }
   377  
   378  func getUniqueTag(sch schema.Schema) uint64 {
   379  	if schema.IsKeyless(sch) {
   380  		panic("keyless tables have no stable column tags")
   381  	}
   382  	return sch.GetPKCols().Tags[0]
   383  }
   384  
   385  func getFkParentSchs(ctx context.Context, root *doltdb.RootValue, fks ...doltdb.ForeignKey) (map[string]schema.Schema, error) {
   386  	schs := make(map[string]schema.Schema)
   387  	for _, toFk := range fks {
   388  		toRefTable, _, ok, err := root.GetTableInsensitive(ctx, toFk.ReferencedTableName)
   389  		if err != nil {
   390  			return nil, err
   391  		}
   392  		if !ok {
   393  			continue // as the schemas are for display-only, we can skip on any missing parents (they were deleted, etc.)
   394  		}
   395  		toRefSch, err := toRefTable.GetSchema(ctx)
   396  		if err != nil {
   397  			return nil, err
   398  		}
   399  		schs[toFk.ReferencedTableName] = toRefSch
   400  	}
   401  	return schs, nil
   402  }
   403  
   404  // IsAdd returns true if the table was added between the fromRoot and toRoot.
   405  func (td TableDelta) IsAdd() bool {
   406  	return td.FromTable == nil && td.ToTable != nil
   407  }
   408  
   409  // IsDrop returns true if the table was dropped between the fromRoot and toRoot.
   410  func (td TableDelta) IsDrop() bool {
   411  	return td.FromTable != nil && td.ToTable == nil
   412  }
   413  
   414  // IsRename return true if the table was renamed between the fromRoot and toRoot.
   415  func (td TableDelta) IsRename() bool {
   416  	if td.IsAdd() || td.IsDrop() {
   417  		return false
   418  	}
   419  	return td.FromName != td.ToName
   420  }
   421  
   422  // CurName returns the most recent name of the table.
   423  func (td TableDelta) CurName() string {
   424  	if td.ToName != "" {
   425  		return td.ToName
   426  	}
   427  	return td.FromName
   428  }
   429  
   430  func (td TableDelta) HasFKChanges() bool {
   431  	return !fkSlicesAreEqual(td.FromFks, td.ToFks)
   432  }
   433  
   434  // GetSchemas returns the table's schema at the fromRoot and toRoot, or schema.Empty if the table did not exist.
   435  func (td TableDelta) GetSchemas(ctx context.Context) (from, to schema.Schema, err error) {
   436  	if td.FromTable != nil {
   437  		from, err = td.FromTable.GetSchema(ctx)
   438  
   439  		if err != nil {
   440  			return nil, nil, err
   441  		}
   442  	} else {
   443  		from = schema.EmptySchema
   444  	}
   445  
   446  	if td.ToTable != nil {
   447  		to, err = td.ToTable.GetSchema(ctx)
   448  
   449  		if err != nil {
   450  			return nil, nil, err
   451  		}
   452  	} else {
   453  		to = schema.EmptySchema
   454  	}
   455  
   456  	return from, to, nil
   457  }
   458  
   459  func (td TableDelta) IsKeyless(ctx context.Context) (bool, error) {
   460  	f, t, err := td.GetSchemas(ctx)
   461  	if err != nil {
   462  		return false, err
   463  	}
   464  
   465  	from, to := schema.IsKeyless(f), schema.IsKeyless(t)
   466  
   467  	if from && to {
   468  		return true, nil
   469  	} else if !from && !to {
   470  		return false, nil
   471  	} else {
   472  		return false, fmt.Errorf("mismatched keyless and keyed schemas for table %s", td.CurName())
   473  	}
   474  }
   475  
   476  // GetMaps returns the table's row map at the fromRoot and toRoot, or and empty map if the table did not exist.
   477  func (td TableDelta) GetMaps(ctx context.Context) (from, to types.Map, err error) {
   478  	if td.FromTable != nil {
   479  		from, err = td.FromTable.GetRowData(ctx)
   480  		if err != nil {
   481  			return from, to, err
   482  		}
   483  	} else {
   484  		from, _ = types.NewMap(ctx, td.ToTable.ValueReadWriter())
   485  	}
   486  
   487  	if td.ToTable != nil {
   488  		to, err = td.ToTable.GetRowData(ctx)
   489  		if err != nil {
   490  			return from, to, err
   491  		}
   492  	} else {
   493  		to, _ = types.NewMap(ctx, td.FromTable.ValueReadWriter())
   494  	}
   495  
   496  	return from, to, nil
   497  }
   498  
   499  func fkSlicesAreEqual(from, to []doltdb.ForeignKey) bool {
   500  	if len(from) != len(to) {
   501  		return false
   502  	}
   503  
   504  	sort.Slice(from, func(i, j int) bool {
   505  		return from[i].Name < from[j].Name
   506  	})
   507  	sort.Slice(to, func(i, j int) bool {
   508  		return to[i].Name < to[j].Name
   509  	})
   510  
   511  	for i := range from {
   512  		if !from[i].DeepEquals(to[i]) {
   513  			return false
   514  		}
   515  	}
   516  	return true
   517  }