github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/writer/noms_write_session.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 writer 16 17 import ( 18 "context" 19 "fmt" 20 "sync" 21 22 "github.com/dolthub/go-mysql-server/sql" 23 "golang.org/x/sync/errgroup" 24 25 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" 26 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 27 "github.com/dolthub/dolt/go/libraries/doltcore/sqle/globalstate" 28 "github.com/dolthub/dolt/go/libraries/doltcore/sqle/index" 29 "github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlutil" 30 "github.com/dolthub/dolt/go/libraries/doltcore/table/editor" 31 "github.com/dolthub/dolt/go/store/types" 32 ) 33 34 // WriteSession encapsulates writes made within a SQL session. 35 // It's responsible for creating and managing the lifecycle of TableWriter's. 36 type WriteSession interface { 37 // GetTableWriter creates a TableWriter and adds it to the WriteSession. 38 GetTableWriter(ctx *sql.Context, tableName doltdb.TableName, db string, setter SessionRootSetter) (TableWriter, error) 39 40 // SetWorkingSet modifies the state of the WriteSession. The WorkingSetRef of |ws| must match the existing Ref. 41 SetWorkingSet(ctx *sql.Context, ws *doltdb.WorkingSet) error 42 43 // GetOptions returns the editor.Options for this session. 44 GetOptions() editor.Options 45 46 // SetOptions sets the editor.Options for this session. 47 SetOptions(opts editor.Options) 48 49 WriteSessionFlusher 50 } 51 52 // WriteSessionFlusher is responsible for flushing any pending edits to the session 53 type WriteSessionFlusher interface { 54 // Flush flushes the pending writes in the session. 55 Flush(ctx *sql.Context) (*doltdb.WorkingSet, error) 56 // FlushWithAutoIncrementOverrides flushes the pending writes in the session, overriding the auto increment values 57 // for any tables provided in the map 58 FlushWithAutoIncrementOverrides(ctx *sql.Context, increment bool, autoIncrements map[string]uint64) (*doltdb.WorkingSet, error) 59 } 60 61 // nomsWriteSession handles all edit operations on a table that may also update other tables. 62 // Serves as coordination for SessionedTableEditors. 63 type nomsWriteSession struct { 64 workingSet *doltdb.WorkingSet 65 tables map[string]*sessionedTableEditor 66 aiTracker globalstate.AutoIncrementTracker 67 mut *sync.RWMutex // This mutex is specifically for changes that affect the TES or all STEs 68 opts editor.Options 69 } 70 71 var _ WriteSession = &nomsWriteSession{} 72 73 // NewWriteSession creates and returns a WriteSession. Inserting a nil root is not an error, as there are 74 // locations that do not have a root at the time of this call. However, a root must be set through SetWorkingRoot before any 75 // table editors are returned. 76 func NewWriteSession(nbf *types.NomsBinFormat, ws *doltdb.WorkingSet, aiTracker globalstate.AutoIncrementTracker, opts editor.Options) WriteSession { 77 if types.IsFormat_DOLT(nbf) { 78 return &prollyWriteSession{ 79 workingSet: ws, 80 tables: make(map[doltdb.TableName]*prollyTableWriter), 81 aiTracker: aiTracker, 82 mut: &sync.RWMutex{}, 83 } 84 } 85 86 return &nomsWriteSession{ 87 workingSet: ws, 88 tables: make(map[string]*sessionedTableEditor), 89 aiTracker: aiTracker, 90 mut: &sync.RWMutex{}, 91 opts: opts, 92 } 93 } 94 95 func (s *nomsWriteSession) GetTableWriter(ctx *sql.Context, table doltdb.TableName, db string, setter SessionRootSetter) (TableWriter, error) { 96 s.mut.Lock() 97 defer s.mut.Unlock() 98 99 t, ok, err := s.workingSet.WorkingRoot().GetTable(ctx, table) 100 if err != nil { 101 return nil, err 102 } 103 if !ok { 104 return nil, doltdb.ErrTableNotFound 105 } 106 vrw := t.ValueReadWriter() 107 108 sch, err := t.GetSchema(ctx) 109 if err != nil { 110 return nil, err 111 } 112 sqlSch, err := sqlutil.FromDoltSchema("", table.Name, sch) 113 if err != nil { 114 return nil, err 115 } 116 117 te, err := s.getTableEditor(ctx, table.Name, sch) 118 if err != nil { 119 return nil, err 120 } 121 122 conv := index.NewKVToSqlRowConverterForCols(t.Format(), sch, nil) 123 124 return &nomsTableWriter{ 125 tableName: table.Name, 126 dbName: db, 127 sch: sch, 128 sqlSch: sqlSch.Schema, 129 vrw: vrw, 130 kvToSQLRow: conv, 131 tableEditor: te, 132 flusher: s, 133 autoInc: s.aiTracker, 134 setter: setter, 135 }, nil 136 } 137 138 // Flush returns an updated root with all of the changed tables. 139 func (s *nomsWriteSession) Flush(ctx *sql.Context) (*doltdb.WorkingSet, error) { 140 s.mut.Lock() 141 defer s.mut.Unlock() 142 return s.flush(ctx) 143 } 144 145 func (s *nomsWriteSession) FlushWithAutoIncrementOverrides(ctx *sql.Context, increment bool, autoIncrements map[string]uint64) (*doltdb.WorkingSet, error) { 146 // auto increment overrides not implemented 147 return s.Flush(ctx) 148 } 149 150 // SetWorkingSet implements WriteSession. 151 func (s *nomsWriteSession) SetWorkingSet(ctx *sql.Context, ws *doltdb.WorkingSet) error { 152 s.mut.Lock() 153 defer s.mut.Unlock() 154 return s.setWorkingSet(ctx, ws) 155 } 156 157 func (s *nomsWriteSession) GetOptions() editor.Options { 158 return s.opts 159 } 160 161 func (s *nomsWriteSession) SetOptions(opts editor.Options) { 162 s.opts = opts 163 } 164 165 // flush is the inner implementation for Flush that does not acquire any locks 166 func (s *nomsWriteSession) flush(ctx *sql.Context) (*doltdb.WorkingSet, error) { 167 newRoot := s.workingSet.WorkingRoot() 168 mu := &sync.Mutex{} 169 rootUpdate := func(name string, table *doltdb.Table) (err error) { 170 mu.Lock() 171 defer mu.Unlock() 172 if newRoot != nil { 173 newRoot, err = newRoot.PutTable(ctx, doltdb.TableName{Name: name}, table) 174 } 175 return err 176 } 177 178 eg, egCtx := errgroup.WithContext(ctx) 179 ctx = ctx.WithContext(egCtx) 180 181 for tblName, tblEditor := range s.tables { 182 if !tblEditor.HasEdits() { 183 continue 184 } 185 186 // copy variables 187 name, ed := tblName, tblEditor 188 189 eg.Go(func() error { 190 tbl, err := ed.tableEditor.Table(ctx) 191 if err != nil { 192 return err 193 } 194 195 // Update the auto increment value for the table if a tracker was provided 196 // TODO: the table probably needs an autoincrement tracker no matter what 197 if schema.HasAutoIncrement(ed.Schema()) { 198 v := s.aiTracker.Current(name) 199 tbl, err = tbl.SetAutoIncrementValue(ctx, v) 200 if err != nil { 201 return err 202 } 203 } 204 205 return rootUpdate(name, tbl) 206 }) 207 } 208 if err := eg.Wait(); err != nil { 209 return nil, err 210 } 211 s.workingSet = s.workingSet.WithWorkingRoot(newRoot) 212 213 return s.workingSet, nil 214 } 215 216 // getTableEditor is the inner implementation for GetTableEditor, allowing recursive calls 217 func (s *nomsWriteSession) getTableEditor(ctx context.Context, tableName string, tableSch schema.Schema) (*sessionedTableEditor, error) { 218 if s.workingSet == nil { 219 return nil, fmt.Errorf("must call SetWorkingSet before a table editor will be returned") 220 } 221 222 var t *doltdb.Table 223 var err error 224 localTableEditor, ok := s.tables[tableName] 225 if ok { 226 if tableSch == nil { 227 return localTableEditor, nil 228 } else if schema.SchemasAreEqual(tableSch, localTableEditor.tableEditor.Schema()) { 229 return localTableEditor, nil 230 } 231 } else { 232 localTableEditor = &sessionedTableEditor{ 233 tableEditSession: s, 234 tableEditor: nil, 235 } 236 s.tables[tableName] = localTableEditor 237 } 238 239 root := s.workingSet.WorkingRoot() 240 241 t, ok, err = root.GetTable(ctx, doltdb.TableName{Name: tableName}) 242 if err != nil { 243 return nil, err 244 } 245 if !ok { 246 return nil, fmt.Errorf("unable to create table editor as `%s` is missing", tableName) 247 } 248 if tableSch == nil { 249 tableSch, err = t.GetSchema(ctx) 250 if err != nil { 251 return nil, err 252 } 253 } 254 255 tableEditor, err := editor.NewTableEditor(ctx, t, tableSch, tableName, s.opts) 256 if err != nil { 257 return nil, err 258 } 259 260 localTableEditor.tableEditor = tableEditor 261 262 return localTableEditor, nil 263 } 264 265 // setRoot is the inner implementation for SetWorkingRoot that does not acquire any locks 266 func (s *nomsWriteSession) setWorkingSet(ctx context.Context, ws *doltdb.WorkingSet) error { 267 if ws == nil { 268 return fmt.Errorf("cannot set a nomsWriteSession's working set to nil once it has been created") 269 } 270 if s.workingSet != nil && s.workingSet.Ref() != ws.Ref() { 271 return fmt.Errorf("cannot change working set ref using SetWorkingSet") 272 } 273 s.workingSet = ws 274 275 root := ws.WorkingRoot() 276 for tableName, localTableEditor := range s.tables { 277 t, ok, err := root.GetTable(ctx, doltdb.TableName{Name: tableName}) 278 if err != nil { 279 return err 280 } 281 if !ok { // table was removed in newer root 282 if err := localTableEditor.tableEditor.Close(ctx); err != nil { 283 return err 284 } 285 delete(s.tables, tableName) 286 continue 287 } 288 tSch, err := t.GetSchema(ctx) 289 if err != nil { 290 return err 291 } 292 293 newTableEditor, err := editor.NewTableEditor(ctx, t, tSch, tableName, s.opts) 294 if err != nil { 295 return err 296 } 297 if err := localTableEditor.tableEditor.Close(ctx); err != nil { 298 return err 299 } 300 localTableEditor.tableEditor = newTableEditor 301 } 302 return nil 303 }