github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/dtables/log_table.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 dtables
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  
    21  	"github.com/dolthub/go-mysql-server/sql"
    22  	"github.com/dolthub/go-mysql-server/sql/types"
    23  
    24  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    25  	"github.com/dolthub/dolt/go/libraries/doltcore/env/actions/commitwalk"
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/index"
    28  	"github.com/dolthub/dolt/go/store/hash"
    29  	"github.com/dolthub/dolt/go/store/prolly"
    30  )
    31  
    32  const logsDefaultRowCount = 100
    33  
    34  // LogTable is a sql.Table implementation that implements a system table which shows the dolt commit log
    35  type LogTable struct {
    36  	dbName            string
    37  	ddb               *doltdb.DoltDB
    38  	head              *doltdb.Commit
    39  	headHash          hash.Hash
    40  	headCommitClosure *prolly.CommitClosure
    41  }
    42  
    43  var _ sql.Table = (*LogTable)(nil)
    44  var _ sql.StatisticsTable = (*LogTable)(nil)
    45  var _ sql.IndexAddressable = (*LogTable)(nil)
    46  
    47  // NewLogTable creates a LogTable
    48  func NewLogTable(_ *sql.Context, dbName string, ddb *doltdb.DoltDB, head *doltdb.Commit) sql.Table {
    49  	return &LogTable{dbName: dbName, ddb: ddb, head: head}
    50  }
    51  
    52  // DataLength implements sql.StatisticsTable
    53  func (dt *LogTable) DataLength(ctx *sql.Context) (uint64, error) {
    54  	numBytesPerRow := schema.SchemaAvgLength(dt.Schema())
    55  	numRows, _, err := dt.RowCount(ctx)
    56  	if err != nil {
    57  		return 0, err
    58  	}
    59  	return numBytesPerRow * numRows, nil
    60  }
    61  
    62  // RowCount implements sql.StatisticsTable
    63  func (dt *LogTable) RowCount(ctx *sql.Context) (uint64, bool, error) {
    64  	cc, err := dt.head.GetCommitClosure(ctx)
    65  	if err != nil {
    66  		// TODO: remove this when we deprecate LD
    67  		return logsDefaultRowCount, false, nil
    68  	}
    69  	if cc.IsEmpty() {
    70  		return 1, true, nil
    71  	}
    72  	cnt, err := cc.Count()
    73  	return uint64(cnt + 1), true, err
    74  }
    75  
    76  // Name is a sql.Table interface function which returns the name of the table which is defined by the constant
    77  // LogTableName
    78  func (dt *LogTable) Name() string {
    79  	return doltdb.LogTableName
    80  }
    81  
    82  // String is a sql.Table interface function which returns the name of the table which is defined by the constant
    83  // LogTableName
    84  func (dt *LogTable) String() string {
    85  	return doltdb.LogTableName
    86  }
    87  
    88  // Schema is a sql.Table interface function that gets the sql.Schema of the log system table.
    89  func (dt *LogTable) Schema() sql.Schema {
    90  	return []*sql.Column{
    91  		{Name: "commit_hash", Type: types.Text, Source: doltdb.LogTableName, PrimaryKey: true, DatabaseSource: dt.dbName},
    92  		{Name: "committer", Type: types.Text, Source: doltdb.LogTableName, PrimaryKey: false, DatabaseSource: dt.dbName},
    93  		{Name: "email", Type: types.Text, Source: doltdb.LogTableName, PrimaryKey: false, DatabaseSource: dt.dbName},
    94  		{Name: "date", Type: types.Datetime, Source: doltdb.LogTableName, PrimaryKey: false, DatabaseSource: dt.dbName},
    95  		{Name: "message", Type: types.Text, Source: doltdb.LogTableName, PrimaryKey: false, DatabaseSource: dt.dbName},
    96  	}
    97  }
    98  
    99  // Collation implements the sql.Table interface.
   100  func (dt *LogTable) Collation() sql.CollationID {
   101  	return sql.Collation_Default
   102  }
   103  
   104  // Partitions is a sql.Table interface function that returns a partition of the data.  Currently the data is unpartitioned.
   105  func (dt *LogTable) Partitions(*sql.Context) (sql.PartitionIter, error) {
   106  	return index.SinglePartitionIterFromNomsMap(nil), nil
   107  }
   108  
   109  // PartitionRows is a sql.Table interface function that gets a row iterator for a partition
   110  func (dt *LogTable) PartitionRows(ctx *sql.Context, p sql.Partition) (sql.RowIter, error) {
   111  	switch p := p.(type) {
   112  	case *doltdb.CommitPart:
   113  		return sql.RowsToRowIter(sql.NewRow(p.Hash().String(), p.Meta().Name, p.Meta().Email, p.Meta().Time(), p.Meta().Description)), nil
   114  	default:
   115  		return NewLogItr(ctx, dt.ddb, dt.head)
   116  	}
   117  }
   118  
   119  func (dt *LogTable) GetIndexes(ctx *sql.Context) ([]sql.Index, error) {
   120  	return index.DoltCommitIndexes(dt.dbName, dt.Name(), dt.ddb, true)
   121  }
   122  
   123  // IndexedAccess implements sql.IndexAddressable
   124  func (dt *LogTable) IndexedAccess(lookup sql.IndexLookup) sql.IndexedTable {
   125  	nt := *dt
   126  	return &nt
   127  }
   128  
   129  // PreciseMatch implements sql.IndexAddressable
   130  func (dt *LogTable) PreciseMatch() bool {
   131  	return true
   132  }
   133  
   134  func (dt *LogTable) LookupPartitions(ctx *sql.Context, lookup sql.IndexLookup) (sql.PartitionIter, error) {
   135  	if lookup.Index.ID() == index.CommitHashIndexId {
   136  		return dt.commitHashPartitionIter(ctx, lookup)
   137  	}
   138  
   139  	return dt.Partitions(ctx)
   140  }
   141  
   142  func (dt *LogTable) commitHashPartitionIter(ctx *sql.Context, lookup sql.IndexLookup) (sql.PartitionIter, error) {
   143  	hashStrs, ok := index.LookupToPointSelectStr(lookup)
   144  	if !ok {
   145  		return nil, fmt.Errorf("failed to parse commit lookup ranges: %s", sql.DebugString(lookup.Ranges))
   146  	}
   147  	hashes, commits, metas := index.HashesToCommits(ctx, dt.ddb, hashStrs, nil, false)
   148  	if len(hashes) == 0 {
   149  		return sql.PartitionsToPartitionIter(), nil
   150  	}
   151  	var partitions []sql.Partition
   152  	for i, h := range hashes {
   153  		height, err := commits[i].Height()
   154  		if err != nil {
   155  			return nil, err
   156  		}
   157  
   158  		ok, err = dt.CommitIsInScope(ctx, height, h)
   159  		if err != nil {
   160  			return nil, err
   161  		}
   162  		if !ok {
   163  			continue
   164  		}
   165  
   166  		partitions = append(partitions, doltdb.NewCommitPart(h, commits[i], metas[i]))
   167  
   168  	}
   169  	return sql.PartitionsToPartitionIter(partitions...), nil
   170  }
   171  
   172  // CommitIsInScope returns true if a given commit hash is head or is
   173  // visible from the current head's ancestry graph.
   174  func (dt *LogTable) CommitIsInScope(ctx context.Context, height uint64, h hash.Hash) (bool, error) {
   175  	headHash, err := dt.HeadHash()
   176  	if err != nil {
   177  		return false, err
   178  	}
   179  	if headHash == h {
   180  		return true, nil
   181  	}
   182  	cc, err := dt.HeadCommitClosure(ctx)
   183  	if err != nil {
   184  		return false, err
   185  	}
   186  	return cc.ContainsKey(ctx, h, height)
   187  }
   188  
   189  func (dt *LogTable) HeadCommitClosure(ctx context.Context) (*prolly.CommitClosure, error) {
   190  	if dt.headCommitClosure == nil {
   191  		cc, err := dt.head.GetCommitClosure(ctx)
   192  		dt.headCommitClosure = &cc
   193  		if err != nil {
   194  			return nil, err
   195  		}
   196  	}
   197  	return dt.headCommitClosure, nil
   198  }
   199  
   200  func (dt *LogTable) HeadHash() (hash.Hash, error) {
   201  	if dt.headHash.IsEmpty() {
   202  		var err error
   203  		dt.headHash, err = dt.head.HashOf()
   204  		if err != nil {
   205  			return hash.Hash{}, err
   206  		}
   207  	}
   208  	return dt.headHash, nil
   209  }
   210  
   211  // LogItr is a sql.RowItr implementation which iterates over each commit as if it's a row in the table.
   212  type LogItr struct {
   213  	child doltdb.CommitItr
   214  }
   215  
   216  // NewLogItr creates a LogItr from the current environment.
   217  func NewLogItr(ctx *sql.Context, ddb *doltdb.DoltDB, head *doltdb.Commit) (*LogItr, error) {
   218  	h, err := head.HashOf()
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  
   223  	child, err := commitwalk.GetTopologicalOrderIterator(ctx, ddb, []hash.Hash{h}, nil)
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  
   228  	return &LogItr{child}, nil
   229  }
   230  
   231  // Next retrieves the next row. It will return io.EOF if it's the last row.
   232  // After retrieving the last row, Close will be automatically closed.
   233  func (itr *LogItr) Next(ctx *sql.Context) (sql.Row, error) {
   234  	h, optCmt, err := itr.child.Next(ctx)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  
   239  	cm, ok := optCmt.ToCommit()
   240  	if !ok {
   241  		// Should have been caught by the commit walk.
   242  		return nil, doltdb.ErrGhostCommitRuntimeFailure
   243  	}
   244  
   245  	meta, err := cm.GetCommitMeta(ctx)
   246  	if err != nil {
   247  		return nil, err
   248  	}
   249  
   250  	return sql.NewRow(h.String(), meta.Name, meta.Email, meta.Time(), meta.Description), nil
   251  }
   252  
   253  // Close closes the iterator.
   254  func (itr *LogItr) Close(*sql.Context) error {
   255  	return nil
   256  }