mirror of
https://github.com/dolthub/dolt.git
synced 2026-02-10 18:49:02 -06:00
414 lines
9.3 KiB
Go
414 lines
9.3 KiB
Go
// Copyright 2019 Liquidata, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package diff
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
|
|
"github.com/liquidata-inc/dolt/go/libraries/doltcore/doltdb"
|
|
"github.com/liquidata-inc/dolt/go/libraries/doltcore/env"
|
|
"github.com/liquidata-inc/dolt/go/libraries/doltcore/schema"
|
|
"github.com/liquidata-inc/dolt/go/store/hash"
|
|
"github.com/liquidata-inc/dolt/go/store/types"
|
|
)
|
|
|
|
type TableDiffType int
|
|
|
|
const (
|
|
AddedTable TableDiffType = iota
|
|
ModifiedTable
|
|
RemovedTable
|
|
)
|
|
|
|
type TableDiffs struct {
|
|
NumAdded int
|
|
NumModified int
|
|
NumRemoved int
|
|
TableToType map[string]TableDiffType
|
|
Tables []string
|
|
}
|
|
|
|
type DocDiffType int
|
|
|
|
const (
|
|
AddedDoc DocDiffType = iota
|
|
ModifiedDoc
|
|
RemovedDoc
|
|
)
|
|
|
|
type DocDiffs struct {
|
|
NumAdded int
|
|
NumModified int
|
|
NumRemoved int
|
|
DocToType map[string]DocDiffType
|
|
Docs []string
|
|
}
|
|
|
|
type RootType int
|
|
|
|
func (rt RootType) String() string {
|
|
switch rt {
|
|
case WorkingRoot:
|
|
return "working root"
|
|
case StagedRoot:
|
|
return "staged root"
|
|
case CommitRoot:
|
|
return "root value for commit"
|
|
case HeadRoot:
|
|
return "HEAD commit root value"
|
|
}
|
|
|
|
return "unknown"
|
|
}
|
|
|
|
const (
|
|
WorkingRoot RootType = iota
|
|
StagedRoot
|
|
CommitRoot
|
|
HeadRoot
|
|
InvalidRoot
|
|
)
|
|
|
|
type RootValueUnreadable struct {
|
|
rootType RootType
|
|
Cause error
|
|
}
|
|
|
|
func (rvu RootValueUnreadable) Error() string {
|
|
return "error: Unable to read " + rvu.rootType.String()
|
|
}
|
|
|
|
// NewTableDiffs returns the TableDiffs between two roots.
|
|
func NewTableDiffs(ctx context.Context, newer, older *doltdb.RootValue) (*TableDiffs, error) {
|
|
deltas, err := GetTableDeltas(ctx, older, newer)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var added []string
|
|
var modified []string
|
|
var removed []string
|
|
|
|
for _, d := range deltas {
|
|
switch {
|
|
case d.IsAdd():
|
|
added = append(added, d.ToName)
|
|
case d.IsDrop():
|
|
removed = append(removed, d.FromName)
|
|
default:
|
|
modified = append(modified, d.ToName)
|
|
}
|
|
}
|
|
|
|
var tbls []string
|
|
tbls = append(tbls, added...)
|
|
tbls = append(tbls, modified...)
|
|
tbls = append(tbls, removed...)
|
|
|
|
tblToType := make(map[string]TableDiffType)
|
|
for _, tbl := range added {
|
|
tblToType[tbl] = AddedTable
|
|
}
|
|
|
|
for _, tbl := range modified {
|
|
tblToType[tbl] = ModifiedTable
|
|
}
|
|
|
|
for _, tbl := range removed {
|
|
tblToType[tbl] = RemovedTable
|
|
}
|
|
|
|
sort.Strings(tbls)
|
|
|
|
return &TableDiffs{len(added), len(modified), len(removed), tblToType, tbls}, err
|
|
}
|
|
|
|
func (td *TableDiffs) Len() int {
|
|
return len(td.Tables)
|
|
}
|
|
|
|
// GetTableDiffs returns the staged and unstaged TableDiffs for the repo.
|
|
func GetTableDiffs(ctx context.Context, dEnv *env.DoltEnv) (*TableDiffs, *TableDiffs, error) {
|
|
headRoot, err := dEnv.HeadRoot(ctx)
|
|
|
|
if err != nil {
|
|
return nil, nil, RootValueUnreadable{HeadRoot, err}
|
|
}
|
|
|
|
stagedRoot, err := dEnv.StagedRoot(ctx)
|
|
|
|
if err != nil {
|
|
return nil, nil, RootValueUnreadable{StagedRoot, err}
|
|
}
|
|
|
|
workingRoot, err := dEnv.WorkingRoot(ctx)
|
|
|
|
if err != nil {
|
|
return nil, nil, RootValueUnreadable{WorkingRoot, err}
|
|
}
|
|
|
|
stagedDiffs, err := NewTableDiffs(ctx, stagedRoot, headRoot)
|
|
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
notStagedDiffs, err := NewTableDiffs(ctx, workingRoot, stagedRoot)
|
|
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return stagedDiffs, notStagedDiffs, nil
|
|
}
|
|
|
|
// NewDocDiffs returns DocDiffs for Dolt Docs between two roots.
|
|
func NewDocDiffs(ctx context.Context, dEnv *env.DoltEnv, older *doltdb.RootValue, newer *doltdb.RootValue, docDetails []doltdb.DocDetails) (*DocDiffs, error) {
|
|
var added []string
|
|
var modified []string
|
|
var removed []string
|
|
if older != nil {
|
|
if newer == nil {
|
|
a, m, r, err := older.DocDiff(ctx, nil, docDetails)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
added = a
|
|
modified = m
|
|
removed = r
|
|
} else {
|
|
a, m, r, err := older.DocDiff(ctx, newer, docDetails)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
added = a
|
|
modified = m
|
|
removed = r
|
|
}
|
|
}
|
|
var docs []string
|
|
docs = append(docs, added...)
|
|
docs = append(docs, modified...)
|
|
docs = append(docs, removed...)
|
|
sort.Strings(docs)
|
|
|
|
docsToType := make(map[string]DocDiffType)
|
|
for _, nt := range added {
|
|
docsToType[nt] = AddedDoc
|
|
}
|
|
|
|
for _, nt := range modified {
|
|
docsToType[nt] = ModifiedDoc
|
|
}
|
|
|
|
for _, nt := range removed {
|
|
docsToType[nt] = RemovedDoc
|
|
}
|
|
|
|
return &DocDiffs{len(added), len(modified), len(removed), docsToType, docs}, nil
|
|
}
|
|
|
|
// Len returns the number of docs in a DocDiffs
|
|
func (nd *DocDiffs) Len() int {
|
|
return len(nd.Docs)
|
|
}
|
|
|
|
// GetDocDiffs retrieves staged and unstaged DocDiffs.
|
|
func GetDocDiffs(ctx context.Context, dEnv *env.DoltEnv) (*DocDiffs, *DocDiffs, error) {
|
|
docDetails, err := dEnv.GetAllValidDocDetails()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
workingRoot, err := dEnv.WorkingRoot(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
notStagedDocDiffs, err := NewDocDiffs(ctx, dEnv, workingRoot, nil, docDetails)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
headRoot, err := dEnv.HeadRoot(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
stagedRoot, err := dEnv.StagedRoot(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
stagedDocDiffs, err := NewDocDiffs(ctx, dEnv, headRoot, stagedRoot, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return stagedDocDiffs, notStagedDocDiffs, nil
|
|
}
|
|
|
|
type TableDelta struct {
|
|
FromName string
|
|
ToName string
|
|
FromTable *doltdb.Table
|
|
ToTable *doltdb.Table
|
|
ToForeignKeys []*doltdb.DisplayForeignKey // In the event that a table is an add, we'll display the FKs as well
|
|
}
|
|
|
|
// GetTableDeltas returns a list of TableDelta objects for each table that changed between fromRoot and toRoot.
|
|
func GetTableDeltas(ctx context.Context, fromRoot, toRoot *doltdb.RootValue) ([]TableDelta, error) {
|
|
var deltas []TableDelta
|
|
fromTable := make(map[uint64]*doltdb.Table)
|
|
fromTableNames := make(map[uint64]string)
|
|
fromTableHashes := make(map[uint64]hash.Hash)
|
|
|
|
err := fromRoot.IterTables(ctx, func(name string, table *doltdb.Table) (stop bool, err error) {
|
|
sch, err := table.GetSchema(ctx)
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
|
|
th, err := table.HashOf()
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
|
|
pkTag := sch.GetPKCols().GetColumns()[0].Tag
|
|
fromTable[pkTag] = table
|
|
fromTableNames[pkTag] = name
|
|
fromTableHashes[pkTag] = th
|
|
return false, nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = toRoot.IterTables(ctx, func(name string, table *doltdb.Table) (stop bool, err error) {
|
|
sch, err := table.GetSchema(ctx)
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
|
|
th, err := table.HashOf()
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
|
|
pkTag := sch.GetPKCols().GetColumns()[0].Tag
|
|
oldName, ok := fromTableNames[pkTag]
|
|
|
|
fkc, err := toRoot.GetForeignKeyCollection(ctx)
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
|
|
toDisplayFks, err := fkc.KeysForDisplay(ctx, name, toRoot)
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
|
|
if !ok {
|
|
deltas = append(deltas, TableDelta{ToName: name, ToTable: table, ToForeignKeys: toDisplayFks})
|
|
} else if oldName != name || fromTableHashes[pkTag] != th {
|
|
deltas = append(deltas, TableDelta{
|
|
ToName: name,
|
|
FromName: fromTableNames[pkTag],
|
|
ToTable: table,
|
|
FromTable: fromTable[pkTag],
|
|
ToForeignKeys: toDisplayFks,
|
|
})
|
|
}
|
|
|
|
if ok {
|
|
delete(fromTableNames, pkTag) // consume table name
|
|
}
|
|
|
|
return false, nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// all unmatched tables in fromRoot must have been dropped
|
|
for pkTag, oldName := range fromTableNames {
|
|
deltas = append(deltas, TableDelta{FromName: oldName, FromTable: fromTable[pkTag]})
|
|
}
|
|
|
|
return deltas, nil
|
|
}
|
|
|
|
// IsAdd returns true if the table was added between the fromRoot and toRoot.
|
|
func (td TableDelta) IsAdd() bool {
|
|
return td.FromTable == nil && td.ToTable != nil
|
|
}
|
|
|
|
// IsDrop returns true if the table was dropped between the fromRoot and toRoot.
|
|
func (td TableDelta) IsDrop() bool {
|
|
return td.FromTable != nil && td.ToTable == nil
|
|
}
|
|
|
|
// GetSchemas returns the table's schema at the fromRoot and toRoot, or schema.Empty if the table did not exist.
|
|
func (td TableDelta) GetSchemas(ctx context.Context) (from, to schema.Schema, err error) {
|
|
if td.FromTable != nil {
|
|
from, err = td.FromTable.GetSchema(ctx)
|
|
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
} else {
|
|
from = schema.EmptySchema
|
|
}
|
|
|
|
if td.ToTable != nil {
|
|
to, err = td.ToTable.GetSchema(ctx)
|
|
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
} else {
|
|
to = schema.EmptySchema
|
|
}
|
|
|
|
return from, to, nil
|
|
}
|
|
|
|
// GetMaps returns the table's row map at the fromRoot and toRoot, or and empty map if the table did not exist.
|
|
func (td TableDelta) GetMaps(ctx context.Context) (from, to types.Map, err error) {
|
|
if td.FromTable != nil {
|
|
from, err = td.FromTable.GetRowData(ctx)
|
|
if err != nil {
|
|
return from, to, err
|
|
}
|
|
} else {
|
|
from, _ = types.NewMap(ctx, td.ToTable.ValueReadWriter())
|
|
}
|
|
|
|
if td.ToTable != nil {
|
|
to, err = td.ToTable.GetRowData(ctx)
|
|
if err != nil {
|
|
return from, to, err
|
|
}
|
|
} else {
|
|
to, _ = types.NewMap(ctx, td.FromTable.ValueReadWriter())
|
|
}
|
|
|
|
return from, to, nil
|
|
}
|