github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/dtables/schema_conflicts_table.go (about)

     1  // Copyright 2020 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 dtables
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"io"
    21  
    22  	"github.com/dolthub/go-mysql-server/sql"
    23  	"github.com/dolthub/go-mysql-server/sql/types"
    24  
    25  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/merge"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    28  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
    29  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlfmt"
    30  	noms "github.com/dolthub/dolt/go/store/types"
    31  )
    32  
    33  var _ sql.Table = (*SchemaConflictsTable)(nil)
    34  
    35  // SchemaConflictsTable is a sql.Table implementation that implements a system table which shows the current conflicts
    36  type SchemaConflictsTable struct {
    37  	dbName string
    38  	ddb    *doltdb.DoltDB
    39  }
    40  
    41  // NewSchemaConflictsTable creates a SchemaConflictsTable
    42  func NewSchemaConflictsTable(_ *sql.Context, dbName string, ddb *doltdb.DoltDB) sql.Table {
    43  	return &SchemaConflictsTable{dbName: dbName, ddb: ddb}
    44  }
    45  
    46  // Name is a sql.Table interface function which returns the name of the table which is defined by the constant
    47  // SchemaConflictsTableName
    48  func (dt *SchemaConflictsTable) Name() string {
    49  	return doltdb.SchemaConflictsTableName
    50  }
    51  
    52  // String is a sql.Table interface function which returns the name of the table which is defined by the constant
    53  // SchemaConflictsTableName
    54  func (dt *SchemaConflictsTable) String() string {
    55  	return doltdb.SchemaConflictsTableName
    56  }
    57  
    58  // Schema is a sql.Table interface function that gets the sql.Schema of the log system table.
    59  func (dt *SchemaConflictsTable) Schema() sql.Schema {
    60  	return []*sql.Column{
    61  		{Name: "table_name", Type: types.Text, Source: doltdb.SchemaConflictsTableName, PrimaryKey: true, DatabaseSource: dt.dbName},
    62  		{Name: "base_schema", Type: types.Text, Source: doltdb.SchemaConflictsTableName, PrimaryKey: false, DatabaseSource: dt.dbName},
    63  		{Name: "our_schema", Type: types.Text, Source: doltdb.SchemaConflictsTableName, PrimaryKey: false, DatabaseSource: dt.dbName},
    64  		{Name: "their_schema", Type: types.Text, Source: doltdb.SchemaConflictsTableName, PrimaryKey: false, DatabaseSource: dt.dbName},
    65  		{Name: "description", Type: types.Text, Source: doltdb.SchemaConflictsTableName, PrimaryKey: false, DatabaseSource: dt.dbName},
    66  	}
    67  }
    68  
    69  // Collation implements the sql.Table interface.
    70  func (dt *SchemaConflictsTable) Collation() sql.CollationID {
    71  	return sql.Collation_Default
    72  }
    73  
    74  // Partitions is a sql.Table interface function that returns a partition of the data.  Conflict data for all tables exists in a single partition.
    75  func (dt *SchemaConflictsTable) Partitions(ctx *sql.Context) (sql.PartitionIter, error) {
    76  	sess := dsess.DSessFromSess(ctx.Session)
    77  	ws, err := sess.WorkingSet(ctx, dt.dbName)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	dbd, _ := sess.GetDbData(ctx, dt.dbName)
    82  
    83  	if ws.MergeState() == nil || !ws.MergeState().HasSchemaConflicts() {
    84  		return sql.PartitionsToPartitionIter(), nil
    85  	}
    86  
    87  	head, err := sess.GetHeadCommit(ctx, dt.dbName)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	return sql.PartitionsToPartitionIter(schemaConflictsPartition{
    93  		state: ws.MergeState(),
    94  		head:  head,
    95  		ddb:   dbd.Ddb,
    96  	}), nil
    97  }
    98  
    99  // PartitionRows is a sql.Table interface function that gets a row iterator for a partition
   100  func (dt *SchemaConflictsTable) PartitionRows(ctx *sql.Context, part sql.Partition) (sql.RowIter, error) {
   101  	p, ok := part.(schemaConflictsPartition)
   102  	if !ok {
   103  		return nil, errors.New("unexpected partition for schema conflicts table")
   104  	}
   105  
   106  	optCmt, err := doltdb.GetCommitAncestor(ctx, p.head, p.state.Commit())
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	base, ok := optCmt.ToCommit()
   111  	if !ok {
   112  		return nil, doltdb.ErrGhostCommitEncountered
   113  	}
   114  
   115  	baseRoot, err := base.GetRootValue(ctx)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	var conflicts []schemaConflict
   121  	err = p.state.IterSchemaConflicts(ctx, p.ddb, func(table string, cnf doltdb.SchemaConflict) error {
   122  
   123  		c, err := newSchemaConflict(ctx, table, baseRoot, cnf)
   124  		if err != nil {
   125  			return err
   126  		}
   127  		conflicts = append(conflicts, c)
   128  		return nil
   129  	})
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  
   134  	return &schemaConflictsIter{
   135  		conflicts: conflicts,
   136  	}, nil
   137  }
   138  
   139  type schemaConflictsPartition struct {
   140  	state *doltdb.MergeState
   141  	head  *doltdb.Commit
   142  	ddb   *doltdb.DoltDB
   143  }
   144  
   145  func (p schemaConflictsPartition) Key() []byte {
   146  	return []byte(doltdb.SchemaConflictsTableName)
   147  }
   148  
   149  type schemaConflict struct {
   150  	table       string
   151  	baseSch     string
   152  	ourSch      string
   153  	theirSch    string
   154  	description string
   155  }
   156  
   157  func newSchemaConflict(ctx context.Context, table string, baseRoot doltdb.RootValue, c doltdb.SchemaConflict) (schemaConflict, error) {
   158  	bs, err := doltdb.GetAllSchemas(ctx, baseRoot)
   159  	if err != nil {
   160  		return schemaConflict{}, err
   161  	}
   162  	baseSch := bs[table]
   163  
   164  	fkc, err := baseRoot.GetForeignKeyCollection(ctx)
   165  	if err != nil {
   166  		return schemaConflict{}, err
   167  	}
   168  	baseFKs, _ := fkc.KeysForTable(table)
   169  
   170  	var base string
   171  	if baseSch != nil {
   172  		var err error
   173  		base, err = getCreateTableStatement(table, baseSch, baseFKs, bs)
   174  		if err != nil {
   175  			return schemaConflict{}, err
   176  		}
   177  	} else {
   178  		base = "<deleted>"
   179  	}
   180  
   181  	var ours string
   182  	if c.ToSch != nil {
   183  		var err error
   184  		ours, err = getCreateTableStatement(table, c.ToSch, c.ToFks, c.ToParentSchemas)
   185  		if err != nil {
   186  			return schemaConflict{}, err
   187  		}
   188  	} else {
   189  		ours = "<deleted>"
   190  	}
   191  
   192  	var theirs string
   193  	if c.FromSch != nil {
   194  		var err error
   195  		theirs, err = getCreateTableStatement(table, c.FromSch, c.FromFks, c.FromParentSchemas)
   196  		if err != nil {
   197  			return schemaConflict{}, err
   198  		}
   199  	} else {
   200  		theirs = "<deleted>"
   201  	}
   202  
   203  	if c.ToSch == nil || c.FromSch == nil {
   204  		return schemaConflict{
   205  			table:       table,
   206  			baseSch:     base,
   207  			ourSch:      ours,
   208  			theirSch:    theirs,
   209  			description: "cannot merge a table deletion with schema modification",
   210  		}, nil
   211  	}
   212  
   213  	desc, err := getSchemaConflictDescription(ctx, table, baseSch, c.ToSch, c.FromSch)
   214  	if err != nil {
   215  		return schemaConflict{}, err
   216  	}
   217  
   218  	return schemaConflict{
   219  		table:       table,
   220  		baseSch:     base,
   221  		ourSch:      ours,
   222  		theirSch:    theirs,
   223  		description: desc,
   224  	}, nil
   225  }
   226  
   227  func getCreateTableStatement(table string, sch schema.Schema, fks []doltdb.ForeignKey, parents map[string]schema.Schema) (string, error) {
   228  	return sqlfmt.GenerateCreateTableStatement(table, sch, fks, parents)
   229  }
   230  
   231  func getSchemaConflictDescription(ctx context.Context, table string, base, ours, theirs schema.Schema) (string, error) {
   232  	nbf := noms.Format_Default
   233  	_, conflict, _, _, err := merge.SchemaMerge(ctx, nbf, ours, theirs, base, table)
   234  	if err != nil {
   235  		return "", err
   236  	}
   237  	return conflict.String(), nil
   238  }
   239  
   240  type schemaConflictsIter struct {
   241  	conflicts   []schemaConflict
   242  	baseSchemas map[string]schema.Schema
   243  	baseCommit  *doltdb.Commit
   244  }
   245  
   246  func (it *schemaConflictsIter) Next(ctx *sql.Context) (sql.Row, error) {
   247  	if len(it.conflicts) == 0 {
   248  		return nil, io.EOF
   249  	}
   250  	c := it.conflicts[0] // pop next conflict
   251  	it.conflicts = it.conflicts[1:]
   252  	return sql.NewRow(c.table, c.baseSch, c.ourSch, c.theirSch, c.description), nil
   253  }
   254  
   255  func (it *schemaConflictsIter) Close(ctx *sql.Context) error {
   256  	it.conflicts = nil
   257  	return nil
   258  }