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 }