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

     1  // Copyright 2019 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  	"github.com/dolthub/go-mysql-server/sql"
    19  
    20  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/globalstate"
    21  
    22  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    23  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    24  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/index"
    25  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlutil"
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/table/editor"
    27  	"github.com/dolthub/dolt/go/store/types"
    28  )
    29  
    30  type TableWriter interface {
    31  	sql.TableEditor
    32  	sql.ForeignKeyEditor
    33  	sql.AutoIncrementSetter
    34  }
    35  
    36  // AutoIncrementGetter is implemented by editors that support AUTO_INCREMENT to return the next value that will be
    37  // inserted.
    38  type AutoIncrementGetter interface {
    39  	GetNextAutoIncrementValue(ctx *sql.Context, insertVal interface{}) (uint64, error)
    40  }
    41  
    42  // SessionRootSetter sets the root value for the session.
    43  type SessionRootSetter func(ctx *sql.Context, dbName string, root doltdb.RootValue) error
    44  
    45  // nomsTableWriter is a wrapper for *doltdb.SessionedTableEditor that complies with the SQL interface.
    46  //
    47  // The nomsTableWriter has two levels of batching: one supported at the SQL engine layer where a single UPDATE, DELETE or
    48  // INSERT statement will touch many rows, and we want to avoid unnecessary intermediate writes; and one at the dolt
    49  // layer as a "batch mode" in DoltDatabase. In the latter mode, it's possible to mix inserts, updates and deletes in any
    50  // order. In general, this is unsafe and will produce incorrect results in many cases. The editor makes reasonable
    51  // attempts to produce correct results when interleaving insert and delete statements, but this is almost entirely to
    52  // support REPLACE statements, which are implemented as a DELETE followed by an INSERT. In general, not flushing the
    53  // editor after every SQL statement is incorrect and will return incorrect results. The single reliable exception is an
    54  // unbroken chain of INSERT statements, where we have taken pains to batch writes to speed things up.
    55  type nomsTableWriter struct {
    56  	tableName   string
    57  	dbName      string
    58  	sch         schema.Schema
    59  	sqlSch      sql.Schema
    60  	vrw         types.ValueReadWriter
    61  	kvToSQLRow  *index.KVToSqlRowConverter
    62  	tableEditor editor.TableEditor
    63  	flusher     WriteSessionFlusher
    64  
    65  	autoInc                globalstate.AutoIncrementTracker
    66  	nextAutoIncrementValue map[string]uint64
    67  
    68  	setter         SessionRootSetter
    69  	errEncountered error
    70  }
    71  
    72  func (te *nomsTableWriter) PreciseMatch() bool {
    73  	return true
    74  }
    75  
    76  var _ TableWriter = &nomsTableWriter{}
    77  var _ AutoIncrementGetter = &nomsTableWriter{}
    78  
    79  func (te *nomsTableWriter) duplicateKeyErrFunc(keyString, indexName string, k, v types.Tuple, isPk bool) error {
    80  	oldRow, err := te.kvToSQLRow.ConvertKVTuplesToSqlRow(k, v)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	return sql.NewUniqueKeyErr(keyString, isPk, oldRow)
    86  }
    87  
    88  func (te *nomsTableWriter) Insert(ctx *sql.Context, sqlRow sql.Row) error {
    89  	if schema.IsKeyless(te.sch) {
    90  		return te.keylessInsert(ctx, sqlRow)
    91  	}
    92  	return te.keyedInsert(ctx, sqlRow)
    93  }
    94  
    95  func (te *nomsTableWriter) keylessInsert(ctx *sql.Context, sqlRow sql.Row) error {
    96  	dRow, err := sqlutil.SqlRowToDoltRow(ctx, te.vrw, sqlRow, te.sch)
    97  	if err != nil {
    98  		return err
    99  	}
   100  	return te.tableEditor.InsertRow(ctx, dRow, te.duplicateKeyErrFunc)
   101  }
   102  
   103  func (te *nomsTableWriter) keyedInsert(ctx *sql.Context, sqlRow sql.Row) error {
   104  	k, v, tagToVal, err := sqlutil.DoltKeyValueAndMappingFromSqlRow(ctx, te.vrw, sqlRow, te.sch)
   105  	if err != nil {
   106  		return err
   107  	}
   108  	return te.tableEditor.InsertKeyVal(ctx, k, v, tagToVal, te.duplicateKeyErrFunc)
   109  }
   110  
   111  func (te *nomsTableWriter) Delete(ctx *sql.Context, sqlRow sql.Row) error {
   112  	if !schema.IsKeyless(te.sch) {
   113  		k, tagToVal, err := sqlutil.DoltKeyAndMappingFromSqlRow(ctx, te.vrw, sqlRow, te.sch)
   114  		if err != nil {
   115  			return err
   116  		}
   117  		return te.tableEditor.DeleteByKey(ctx, k, tagToVal)
   118  	} else {
   119  		dRow, err := sqlutil.SqlRowToDoltRow(ctx, te.vrw, sqlRow, te.sch)
   120  		if err != nil {
   121  			return err
   122  		}
   123  		return te.tableEditor.DeleteRow(ctx, dRow)
   124  	}
   125  }
   126  
   127  func (te *nomsTableWriter) Update(ctx *sql.Context, oldRow sql.Row, newRow sql.Row) error {
   128  	dOldRow, err := sqlutil.SqlRowToDoltRow(ctx, te.vrw, oldRow, te.sch)
   129  	if err != nil {
   130  		return err
   131  	}
   132  	dNewRow, err := sqlutil.SqlRowToDoltRow(ctx, te.vrw, newRow, te.sch)
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	return te.tableEditor.UpdateRow(ctx, dOldRow, dNewRow, te.duplicateKeyErrFunc)
   138  }
   139  
   140  func (te *nomsTableWriter) GetNextAutoIncrementValue(ctx *sql.Context, insertVal interface{}) (uint64, error) {
   141  	return te.autoInc.Next(te.tableName, insertVal)
   142  }
   143  
   144  func (te *nomsTableWriter) SetAutoIncrementValue(ctx *sql.Context, val uint64) error {
   145  	seq, err := te.autoInc.CoerceAutoIncrementValue(val)
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	te.nextAutoIncrementValue = make(map[string]uint64)
   151  	te.nextAutoIncrementValue[te.tableName] = seq
   152  
   153  	return te.flush(ctx)
   154  }
   155  
   156  func (te *nomsTableWriter) AcquireAutoIncrementLock(ctx *sql.Context) (func(), error) {
   157  	return te.autoInc.AcquireTableLock(ctx, te.tableName)
   158  }
   159  
   160  func (te *nomsTableWriter) IndexedAccess(i sql.IndexLookup) sql.IndexedTable {
   161  	idx := index.DoltIndexFromSqlIndex(i.Index)
   162  	return &nomsFkIndexer{
   163  		writer:  te,
   164  		idxName: idx.ID(),
   165  		idxSch:  idx.IndexSchema(),
   166  	}
   167  }
   168  
   169  func (te *nomsTableWriter) GetIndexes(ctx *sql.Context) ([]sql.Index, error) {
   170  	indexes := ctx.GetIndexRegistry().IndexesByTable(te.dbName, te.tableName)
   171  	ret := make([]sql.Index, len(indexes))
   172  	for i := range indexes {
   173  		ret[i] = indexes[i]
   174  	}
   175  	return ret, nil
   176  }
   177  
   178  // Close implements Closer
   179  func (te *nomsTableWriter) Close(ctx *sql.Context) error {
   180  	if te.errEncountered == nil {
   181  		return te.flush(ctx)
   182  	}
   183  	return nil
   184  }
   185  
   186  // StatementBegin implements the interface sql.TableEditor.
   187  func (te *nomsTableWriter) StatementBegin(ctx *sql.Context) {
   188  	// Table writers are reused in a session, which means we need to reset the error state resulting from previous
   189  	// errors on every new statement.
   190  	te.errEncountered = nil
   191  	te.tableEditor.StatementStarted(ctx)
   192  }
   193  
   194  // DiscardChanges implements the interface sql.TableEditor.
   195  func (te *nomsTableWriter) DiscardChanges(ctx *sql.Context, errorEncountered error) error {
   196  	if _, ignored := errorEncountered.(sql.IgnorableError); !ignored {
   197  		te.errEncountered = errorEncountered
   198  	}
   199  	return te.tableEditor.StatementFinished(ctx, true)
   200  }
   201  
   202  // StatementComplete implements the interface sql.TableEditor.
   203  func (te *nomsTableWriter) StatementComplete(ctx *sql.Context) error {
   204  	return te.tableEditor.StatementFinished(ctx, false)
   205  }
   206  
   207  func (te *nomsTableWriter) flush(ctx *sql.Context) error {
   208  	ws, err := te.flusher.Flush(ctx)
   209  	if err != nil {
   210  		return err
   211  	}
   212  	return te.setter(ctx, te.dbName, ws.WorkingRoot())
   213  }
   214  
   215  func autoIncrementColFromSchema(sch schema.Schema) schema.Column {
   216  	var autoCol schema.Column
   217  	_ = sch.GetAllCols().Iter(func(tag uint64, col schema.Column) (stop bool, err error) {
   218  		if col.AutoIncrement {
   219  			autoCol = col
   220  			stop = true
   221  		}
   222  		return
   223  	})
   224  	return autoCol
   225  }