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

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