github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/dtables/ignore_table.go (about) 1 // Copyright 2023 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 "fmt" 19 20 "github.com/dolthub/go-mysql-server/sql" 21 sqlTypes "github.com/dolthub/go-mysql-server/sql/types" 22 23 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" 24 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 25 "github.com/dolthub/dolt/go/libraries/doltcore/schema/typeinfo" 26 "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" 27 "github.com/dolthub/dolt/go/libraries/doltcore/sqle/index" 28 "github.com/dolthub/dolt/go/libraries/doltcore/sqle/writer" 29 "github.com/dolthub/dolt/go/store/hash" 30 "github.com/dolthub/dolt/go/store/types" 31 ) 32 33 var _ sql.Table = (*IgnoreTable)(nil) 34 var _ sql.UpdatableTable = (*IgnoreTable)(nil) 35 var _ sql.DeletableTable = (*IgnoreTable)(nil) 36 var _ sql.InsertableTable = (*IgnoreTable)(nil) 37 var _ sql.ReplaceableTable = (*IgnoreTable)(nil) 38 var _ sql.IndexAddressableTable = (*IgnoreTable)(nil) 39 40 // IgnoreTable is the system table that stores patterns for table names that should not be committed. 41 type IgnoreTable struct { 42 backingTable VersionableTable 43 } 44 45 func (i *IgnoreTable) Name() string { 46 return doltdb.IgnoreTableName 47 } 48 49 func (i *IgnoreTable) String() string { 50 return doltdb.IgnoreTableName 51 } 52 53 // Schema is a sql.Table interface function that gets the sql.Schema of the dolt_ignore system table. 54 func (i *IgnoreTable) Schema() sql.Schema { 55 return []*sql.Column{ 56 {Name: "pattern", Type: sqlTypes.Text, Source: doltdb.IgnoreTableName, PrimaryKey: true}, 57 {Name: "ignored", Type: sqlTypes.Boolean, Source: doltdb.IgnoreTableName, PrimaryKey: false, Nullable: false}, 58 } 59 } 60 61 func (i *IgnoreTable) Collation() sql.CollationID { 62 return sql.Collation_Default 63 } 64 65 // Partitions is a sql.Table interface function that returns a partition of the data. 66 func (i *IgnoreTable) Partitions(context *sql.Context) (sql.PartitionIter, error) { 67 if i.backingTable == nil { 68 // no backing table; return an empty iter. 69 return index.SinglePartitionIterFromNomsMap(nil), nil 70 } 71 return i.backingTable.Partitions(context) 72 } 73 74 func (i *IgnoreTable) PartitionRows(context *sql.Context, partition sql.Partition) (sql.RowIter, error) { 75 if i.backingTable == nil { 76 // no backing table; return an empty iter. 77 return sql.RowsToRowIter(), nil 78 } 79 80 return i.backingTable.PartitionRows(context, partition) 81 } 82 83 // NewIgnoreTable creates an IgnoreTable 84 func NewIgnoreTable(_ *sql.Context, backingTable VersionableTable) sql.Table { 85 return &IgnoreTable{backingTable: backingTable} 86 } 87 88 // NewEmptyIgnoreTable creates an IgnoreTable 89 func NewEmptyIgnoreTable(_ *sql.Context) sql.Table { 90 return &IgnoreTable{} 91 } 92 93 // Replacer returns a RowReplacer for this table. The RowReplacer will have Insert and optionally Delete called once 94 // for each row, followed by a call to Close() when all rows have been processed. 95 func (it *IgnoreTable) Replacer(ctx *sql.Context) sql.RowReplacer { 96 return newIgnoreWriter(it) 97 } 98 99 // Updater returns a RowUpdater for this table. The RowUpdater will have Update called once for each row to be 100 // updated, followed by a call to Close() when all rows have been processed. 101 func (it *IgnoreTable) Updater(ctx *sql.Context) sql.RowUpdater { 102 return newIgnoreWriter(it) 103 } 104 105 // Inserter returns an Inserter for this table. The Inserter will get one call to Insert() for each row to be 106 // inserted, and will end with a call to Close() to finalize the insert operation. 107 func (it *IgnoreTable) Inserter(*sql.Context) sql.RowInserter { 108 return newIgnoreWriter(it) 109 } 110 111 // Deleter returns a RowDeleter for this table. The RowDeleter will get one call to Delete for each row to be deleted, 112 // and will end with a call to Close() to finalize the delete operation. 113 func (it *IgnoreTable) Deleter(*sql.Context) sql.RowDeleter { 114 return newIgnoreWriter(it) 115 } 116 117 func (it *IgnoreTable) LockedToRoot(ctx *sql.Context, root doltdb.RootValue) (sql.IndexAddressableTable, error) { 118 if it.backingTable == nil { 119 return it, nil 120 } 121 return it.backingTable.LockedToRoot(ctx, root) 122 } 123 124 // IndexedAccess implements IndexAddressableTable, but IgnoreTables has no indexes. 125 // Thus, this should never be called. 126 func (it *IgnoreTable) IndexedAccess(lookup sql.IndexLookup) sql.IndexedTable { 127 panic("Unreachable") 128 } 129 130 // GetIndexes implements IndexAddressableTable, but IgnoreTables has no indexes. 131 func (it *IgnoreTable) GetIndexes(ctx *sql.Context) ([]sql.Index, error) { 132 return nil, nil 133 } 134 135 func (i *IgnoreTable) PreciseMatch() bool { 136 return true 137 } 138 139 var _ sql.RowReplacer = (*ignoreWriter)(nil) 140 var _ sql.RowUpdater = (*ignoreWriter)(nil) 141 var _ sql.RowInserter = (*ignoreWriter)(nil) 142 var _ sql.RowDeleter = (*ignoreWriter)(nil) 143 144 type ignoreWriter struct { 145 it *IgnoreTable 146 errDuringStatementBegin error 147 prevHash *hash.Hash 148 tableWriter writer.TableWriter 149 } 150 151 func newIgnoreWriter(it *IgnoreTable) *ignoreWriter { 152 return &ignoreWriter{it, nil, nil, nil} 153 } 154 155 // Insert inserts the row given, returning an error if it cannot. Insert will be called once for each row to process 156 // for the insert operation, which may involve many rows. After all rows in an operation have been processed, Close 157 // is called. 158 func (iw *ignoreWriter) Insert(ctx *sql.Context, r sql.Row) error { 159 if err := iw.errDuringStatementBegin; err != nil { 160 return err 161 } 162 return iw.tableWriter.Insert(ctx, r) 163 } 164 165 // Update the given row. Provides both the old and new rows. 166 func (iw *ignoreWriter) Update(ctx *sql.Context, old sql.Row, new sql.Row) error { 167 if err := iw.errDuringStatementBegin; err != nil { 168 return err 169 } 170 return iw.tableWriter.Update(ctx, old, new) 171 } 172 173 // Delete deletes the given row. Returns ErrDeleteRowNotFound if the row was not found. Delete will be called once for 174 // each row to process for the delete operation, which may involve many rows. After all rows have been processed, 175 // Close is called. 176 func (iw *ignoreWriter) Delete(ctx *sql.Context, r sql.Row) error { 177 if err := iw.errDuringStatementBegin; err != nil { 178 return err 179 } 180 return iw.tableWriter.Delete(ctx, r) 181 } 182 183 // StatementBegin is called before the first operation of a statement. Integrators should mark the state of the data 184 // in some way that it may be returned to in the case of an error. 185 func (iw *ignoreWriter) StatementBegin(ctx *sql.Context) { 186 dbName := ctx.GetCurrentDatabase() 187 dSess := dsess.DSessFromSess(ctx.Session) 188 189 // TODO: this needs to use a revision qualified name 190 roots, _ := dSess.GetRoots(ctx, dbName) 191 dbState, ok, err := dSess.LookupDbState(ctx, dbName) 192 if err != nil { 193 iw.errDuringStatementBegin = err 194 return 195 } 196 if !ok { 197 iw.errDuringStatementBegin = fmt.Errorf("no root value found in session") 198 return 199 } 200 201 prevHash, err := roots.Working.HashOf() 202 if err != nil { 203 iw.errDuringStatementBegin = err 204 return 205 } 206 207 iw.prevHash = &prevHash 208 209 found, err := roots.Working.HasTable(ctx, doltdb.IgnoreTableName) 210 211 if err != nil { 212 iw.errDuringStatementBegin = err 213 return 214 } 215 216 if !found { 217 // TODO: This is effectively a duplicate of the schema declaration above in a different format. 218 // We should find a way to not repeat ourselves. 219 colCollection := schema.NewColCollection( 220 schema.Column{ 221 Name: "pattern", 222 Tag: schema.DoltIgnorePatternTag, 223 Kind: types.StringKind, 224 IsPartOfPK: true, 225 TypeInfo: typeinfo.FromKind(types.StringKind), 226 Default: "", 227 AutoIncrement: false, 228 Comment: "", 229 Constraints: nil, 230 }, 231 schema.Column{ 232 Name: "ignored", 233 Tag: schema.DoltIgnoreIgnoredTag, 234 Kind: types.BoolKind, 235 IsPartOfPK: false, 236 TypeInfo: typeinfo.FromKind(types.BoolKind), 237 Default: "", 238 AutoIncrement: false, 239 Comment: "", 240 Constraints: nil, 241 }, 242 ) 243 244 newSchema, err := schema.NewSchema(colCollection, nil, schema.Collation_Default, nil, nil) 245 if err != nil { 246 iw.errDuringStatementBegin = err 247 return 248 } 249 250 // underlying table doesn't exist. Record this, then create the table. 251 newRootValue, err := doltdb.CreateEmptyTable(ctx, roots.Working, doltdb.TableName{Name: doltdb.IgnoreTableName}, newSchema) 252 253 if err != nil { 254 iw.errDuringStatementBegin = err 255 return 256 } 257 258 if dbState.WorkingSet() == nil { 259 iw.errDuringStatementBegin = doltdb.ErrOperationNotSupportedInDetachedHead 260 return 261 } 262 263 // We use WriteSession.SetWorkingSet instead of DoltSession.SetWorkingRoot because we want to avoid modifying the root 264 // until the end of the transaction, but we still want the WriteSession to be able to find the newly 265 // created table. 266 if ws := dbState.WriteSession(); ws != nil { 267 err = ws.SetWorkingSet(ctx, dbState.WorkingSet().WithWorkingRoot(newRootValue)) 268 if err != nil { 269 iw.errDuringStatementBegin = err 270 return 271 } 272 } 273 274 dSess.SetWorkingRoot(ctx, dbName, newRootValue) 275 } 276 277 if ws := dbState.WriteSession(); ws != nil { 278 tableWriter, err := ws.GetTableWriter(ctx, doltdb.TableName{Name: doltdb.IgnoreTableName}, dbName, dSess.SetWorkingRoot) 279 if err != nil { 280 iw.errDuringStatementBegin = err 281 return 282 } 283 iw.tableWriter = tableWriter 284 tableWriter.StatementBegin(ctx) 285 } 286 } 287 288 // DiscardChanges is called if a statement encounters an error, and all current changes since the statement beginning 289 // should be discarded. 290 func (iw *ignoreWriter) DiscardChanges(ctx *sql.Context, errorEncountered error) error { 291 if iw.tableWriter != nil { 292 return iw.tableWriter.DiscardChanges(ctx, errorEncountered) 293 } 294 return nil 295 } 296 297 // StatementComplete is called after the last operation of the statement, indicating that it has successfully completed. 298 // The mark set in StatementBegin may be removed, and a new one should be created on the next StatementBegin. 299 func (iw *ignoreWriter) StatementComplete(ctx *sql.Context) error { 300 return iw.tableWriter.StatementComplete(ctx) 301 } 302 303 // Close finalizes the delete operation, persisting the result. 304 func (iw ignoreWriter) Close(ctx *sql.Context) error { 305 if iw.tableWriter != nil { 306 return iw.tableWriter.Close(ctx) 307 } 308 return nil 309 }