github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/sqle/dtables/history_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  	"io"
    20  	"strings"
    21  
    22  	"github.com/dolthub/go-mysql-server/sql"
    23  	"github.com/dolthub/go-mysql-server/sql/expression"
    24  
    25  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/rowconv"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    28  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlutil"
    29  	"github.com/dolthub/dolt/go/libraries/doltcore/table"
    30  	"github.com/dolthub/dolt/go/libraries/utils/set"
    31  	"github.com/dolthub/dolt/go/store/hash"
    32  	"github.com/dolthub/dolt/go/store/types"
    33  )
    34  
    35  const (
    36  	// DoltHistoryTablePrefix is the name prefix for each history table
    37  
    38  	// CommitHashCol is the name of the column containing the commit hash in the result set
    39  	CommitHashCol = "commit_hash"
    40  
    41  	// CommitterCol is the name of the column containing the committer in the result set
    42  	CommitterCol = "committer"
    43  
    44  	// CommitDateCol is the name of the column containing the commit date in the result set
    45  	CommitDateCol = "commit_date"
    46  )
    47  
    48  var _ sql.Table = (*HistoryTable)(nil)
    49  var _ sql.FilteredTable = (*HistoryTable)(nil)
    50  
    51  // HistoryTable is a system table that shows the history of rows over time
    52  type HistoryTable struct {
    53  	name                  string
    54  	ddb                   *doltdb.DoltDB
    55  	ss                    *schema.SuperSchema
    56  	sqlSch                sql.Schema
    57  	commitFilters         []sql.Expression
    58  	rowFilters            []sql.Expression
    59  	cmItr                 doltdb.CommitItr
    60  	readerCreateFuncCache *ThreadSafeCRFuncCache
    61  }
    62  
    63  // NewHistoryTable creates a history table
    64  func NewHistoryTable(ctx *sql.Context, tblName string, ddb *doltdb.DoltDB, root *doltdb.RootValue, head *doltdb.Commit) (sql.Table, error) {
    65  	ss, err := calcSuperSchema(ctx, root, tblName)
    66  
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	_ = ss.AddColumn(schema.NewColumn(CommitHashCol, schema.HistoryCommitHashTag, types.StringKind, false))
    72  	_ = ss.AddColumn(schema.NewColumn(CommitterCol, schema.HistoryCommitterTag, types.StringKind, false))
    73  	_ = ss.AddColumn(schema.NewColumn(CommitDateCol, schema.HistoryCommitDateTag, types.TimestampKind, false))
    74  
    75  	sch, err := ss.GenerateSchema()
    76  
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	if sch.GetAllCols().Size() <= 3 {
    82  		return nil, sql.ErrTableNotFound.New(doltdb.DoltHistoryTablePrefix + tblName)
    83  	}
    84  
    85  	tableName := doltdb.DoltHistoryTablePrefix + tblName
    86  	sqlSch, err := sqlutil.FromDoltSchema(tableName, sch)
    87  
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	cmItr := doltdb.CommitItrForRoots(ddb, head)
    93  	return &HistoryTable{
    94  		name:                  tblName,
    95  		ddb:                   ddb,
    96  		ss:                    ss,
    97  		sqlSch:                sqlSch,
    98  		cmItr:                 cmItr,
    99  		readerCreateFuncCache: NewThreadSafeCRFuncCache(),
   100  	}, nil
   101  }
   102  
   103  // HandledFilters returns the list of filters that will be handled by the table itself
   104  func (ht *HistoryTable) HandledFilters(filters []sql.Expression) []sql.Expression {
   105  	ht.commitFilters, ht.rowFilters = splitCommitFilters(filters)
   106  	return ht.commitFilters
   107  }
   108  
   109  // Filters returns the list of filters that are applied to this table.
   110  func (ht *HistoryTable) Filters() []sql.Expression {
   111  	return ht.commitFilters
   112  }
   113  
   114  // WithFilters returns a new sql.Table instance with the filters applied
   115  func (ht *HistoryTable) WithFilters(ctx *sql.Context, filters []sql.Expression) sql.Table {
   116  	if ht.commitFilters == nil {
   117  		ht.commitFilters, ht.rowFilters = splitCommitFilters(filters)
   118  	}
   119  
   120  	if len(ht.commitFilters) > 0 {
   121  		commitCheck, err := getCommitFilterFunc(ctx, ht.commitFilters)
   122  
   123  		if err != nil {
   124  			return sqlutil.NewStaticErrorTable(ht, err)
   125  		}
   126  
   127  		ht.cmItr = doltdb.NewFilteringCommitItr(ht.cmItr, commitCheck)
   128  	}
   129  
   130  	return ht
   131  }
   132  
   133  var commitFilterCols = set.NewStrSet([]string{CommitHashCol, CommitDateCol, CommitterCol})
   134  
   135  func getColumnFilterCheck(colNameSet *set.StrSet) func(sql.Expression) bool {
   136  	return func(filter sql.Expression) bool {
   137  		isCommitFilter := true
   138  		sql.Inspect(filter, func(e sql.Expression) (cont bool) {
   139  			if e == nil {
   140  				return true
   141  			}
   142  
   143  			switch val := e.(type) {
   144  			case *expression.GetField:
   145  				if !colNameSet.Contains(strings.ToLower(val.Name())) {
   146  					isCommitFilter = false
   147  					return false
   148  				}
   149  			}
   150  
   151  			return true
   152  		})
   153  
   154  		return isCommitFilter
   155  	}
   156  }
   157  
   158  func splitFilters(filters []sql.Expression, filterCheck func(filter sql.Expression) bool) (matching, notMatching []sql.Expression) {
   159  	matching = make([]sql.Expression, 0, len(filters))
   160  	notMatching = make([]sql.Expression, 0, len(filters))
   161  	for _, f := range filters {
   162  		if filterCheck(f) {
   163  			matching = append(matching, f)
   164  		} else {
   165  			notMatching = append(notMatching, f)
   166  		}
   167  	}
   168  	return matching, notMatching
   169  }
   170  
   171  func splitCommitFilters(filters []sql.Expression) (commitFilters, rowFilters []sql.Expression) {
   172  	return splitFilters(filters, getColumnFilterCheck(commitFilterCols))
   173  }
   174  
   175  func getCommitFilterFunc(ctx *sql.Context, filters []sql.Expression) (doltdb.CommitFilter, error) {
   176  	filters = transformFilters(ctx, filters...)
   177  
   178  	return func(ctx context.Context, h hash.Hash, cm *doltdb.Commit) (filterOut bool, err error) {
   179  		meta, err := cm.GetCommitMeta()
   180  
   181  		if err != nil {
   182  			return false, err
   183  		}
   184  
   185  		sc := sql.NewContext(ctx)
   186  		r := sql.Row{h.String(), meta.Name, meta.Time()}
   187  
   188  		for _, filter := range filters {
   189  			res, err := filter.Eval(sc, r)
   190  			if err != nil {
   191  				return false, err
   192  			}
   193  			b, ok := res.(bool)
   194  			if ok && !b {
   195  				return true, nil
   196  			}
   197  		}
   198  
   199  		return false, err
   200  	}, nil
   201  }
   202  
   203  func transformFilters(ctx *sql.Context, filters ...sql.Expression) []sql.Expression {
   204  	for i := range filters {
   205  		filters[i], _ = expression.TransformUp(ctx, filters[i], func(e sql.Expression) (sql.Expression, error) {
   206  			gf, ok := e.(*expression.GetField)
   207  			if !ok {
   208  				return e, nil
   209  			}
   210  			switch gf.Name() {
   211  			case CommitHashCol:
   212  				return gf.WithIndex(0), nil
   213  			case CommitterCol:
   214  				return gf.WithIndex(1), nil
   215  			case CommitDateCol:
   216  				return gf.WithIndex(2), nil
   217  			default:
   218  				return gf, nil
   219  			}
   220  		})
   221  	}
   222  	return filters
   223  }
   224  
   225  func (ht *HistoryTable) WithProjection(colNames []string) sql.Table {
   226  	return ht
   227  }
   228  
   229  func (ht *HistoryTable) Projection() []string {
   230  	return []string{}
   231  }
   232  
   233  // Name returns the name of the history table
   234  func (ht *HistoryTable) Name() string {
   235  	return doltdb.DoltHistoryTablePrefix + ht.name
   236  }
   237  
   238  // String returns the name of the history table
   239  func (ht *HistoryTable) String() string {
   240  	return doltdb.DoltHistoryTablePrefix + ht.name
   241  }
   242  
   243  // Schema returns the schema for the history table, which will be the super set of the schemas from the history
   244  func (ht *HistoryTable) Schema() sql.Schema {
   245  	return ht.sqlSch
   246  }
   247  
   248  // Partitions returns a PartitionIter which will be used in getting partitions each of which is used to create RowIter.
   249  func (ht *HistoryTable) Partitions(ctx *sql.Context) (sql.PartitionIter, error) {
   250  	return &commitPartitioner{ctx, ht.cmItr}, nil
   251  }
   252  
   253  // PartitionRows takes a partition and returns a row iterator for that partition
   254  func (ht *HistoryTable) PartitionRows(ctx *sql.Context, part sql.Partition) (sql.RowIter, error) {
   255  	cp := part.(*commitPartition)
   256  
   257  	return newRowItrForTableAtCommit(ctx, cp.h, cp.cm, ht.name, ht.ss, ht.rowFilters, ht.readerCreateFuncCache)
   258  }
   259  
   260  // commitPartition is a single commit
   261  type commitPartition struct {
   262  	h  hash.Hash
   263  	cm *doltdb.Commit
   264  }
   265  
   266  // Key returns the hash of the commit for this partition which is used as the partition key
   267  func (cp *commitPartition) Key() []byte {
   268  	return cp.h[:]
   269  }
   270  
   271  // commitPartitioner creates partitions from a CommitItr
   272  type commitPartitioner struct {
   273  	ctx   *sql.Context
   274  	cmItr doltdb.CommitItr
   275  }
   276  
   277  // Next returns the next partition and nil, io.EOF when complete
   278  func (cp commitPartitioner) Next() (sql.Partition, error) {
   279  	h, cm, err := cp.cmItr.Next(cp.ctx)
   280  
   281  	if err != nil {
   282  		return nil, err
   283  	}
   284  
   285  	return &commitPartition{h, cm}, nil
   286  }
   287  
   288  // Close closes the partitioner
   289  func (cp commitPartitioner) Close(*sql.Context) error {
   290  	return nil
   291  }
   292  
   293  type rowItrForTableAtCommit struct {
   294  	ctx            context.Context
   295  	rd             table.TableReadCloser
   296  	sch            schema.Schema
   297  	toSuperSchConv *rowconv.RowConverter
   298  	extraVals      map[uint64]types.Value
   299  	empty          bool
   300  }
   301  
   302  func newRowItrForTableAtCommit(
   303  	ctx context.Context,
   304  	h hash.Hash,
   305  	cm *doltdb.Commit,
   306  	tblName string,
   307  	ss *schema.SuperSchema,
   308  	filters []sql.Expression,
   309  	readerCreateFuncCache *ThreadSafeCRFuncCache) (*rowItrForTableAtCommit, error) {
   310  	root, err := cm.GetRootValue()
   311  
   312  	if err != nil {
   313  		return nil, err
   314  	}
   315  
   316  	tbl, _, ok, err := root.GetTableInsensitive(ctx, tblName)
   317  
   318  	if err != nil {
   319  		return nil, err
   320  	}
   321  
   322  	if !ok {
   323  		return &rowItrForTableAtCommit{empty: true}, nil
   324  	}
   325  
   326  	m, err := tbl.GetRowData(ctx)
   327  
   328  	if err != nil {
   329  		return nil, err
   330  	}
   331  
   332  	schRef, err := tbl.GetSchemaRef()
   333  	schHash := schRef.TargetHash()
   334  
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  
   339  	tblSch, err := doltdb.RefToSchema(ctx, root.VRW(), schRef)
   340  
   341  	if err != nil {
   342  		return nil, err
   343  	}
   344  
   345  	vrw := types.NewMemoryValueStore() // We're displaying here, so all values that require a VRW will use an internal one
   346  
   347  	toSuperSchConv, err := rowConvForSchema(ctx, vrw, ss, tblSch)
   348  
   349  	if err != nil {
   350  		return nil, err
   351  	}
   352  
   353  	createReaderFunc, err := readerCreateFuncCache.GetOrCreate(schHash, tbl.Format(), tblSch, filters)
   354  
   355  	if err != nil {
   356  		return nil, err
   357  	}
   358  
   359  	rd, err := createReaderFunc(ctx, m)
   360  
   361  	if err != nil {
   362  		return nil, err
   363  	}
   364  
   365  	sch, err := ss.GenerateSchema()
   366  
   367  	if err != nil {
   368  		return nil, err
   369  	}
   370  
   371  	hashCol, hashOK := sch.GetAllCols().GetByName(CommitHashCol)
   372  	dateCol, dateOK := sch.GetAllCols().GetByName(CommitDateCol)
   373  	committerCol, commiterOK := sch.GetAllCols().GetByName(CommitterCol)
   374  
   375  	if !hashOK || !dateOK || !commiterOK {
   376  		panic("Bug: History table super schema should always have commit_hash")
   377  	}
   378  
   379  	meta, err := cm.GetCommitMeta()
   380  
   381  	if err != nil {
   382  		return nil, err
   383  	}
   384  
   385  	return &rowItrForTableAtCommit{
   386  		ctx:            ctx,
   387  		rd:             rd,
   388  		sch:            sch,
   389  		toSuperSchConv: toSuperSchConv,
   390  		extraVals: map[uint64]types.Value{
   391  			hashCol.Tag:      types.String(h.String()),
   392  			dateCol.Tag:      types.Timestamp(meta.Time()),
   393  			committerCol.Tag: types.String(meta.Name),
   394  		},
   395  		empty: false,
   396  	}, nil
   397  }
   398  
   399  // Next retrieves the next row. It will return io.EOF if it's the last row. After retrieving the last row, Close
   400  // will be automatically closed.
   401  func (tblItr *rowItrForTableAtCommit) Next() (sql.Row, error) {
   402  	if tblItr.empty {
   403  		return nil, io.EOF
   404  	}
   405  
   406  	r, err := tblItr.rd.ReadRow(tblItr.ctx)
   407  
   408  	if err != nil {
   409  		return nil, err
   410  	}
   411  
   412  	r, err = tblItr.toSuperSchConv.Convert(r)
   413  
   414  	if err != nil {
   415  		return nil, err
   416  	}
   417  
   418  	for tag, val := range tblItr.extraVals {
   419  		r, err = r.SetColVal(tag, val, tblItr.sch)
   420  
   421  		if err != nil {
   422  			return nil, err
   423  		}
   424  	}
   425  
   426  	return sqlutil.DoltRowToSqlRow(r, tblItr.sch)
   427  }
   428  
   429  // Close the iterator.
   430  func (tblItr *rowItrForTableAtCommit) Close(*sql.Context) error {
   431  	if tblItr.rd != nil {
   432  		return tblItr.rd.Close(tblItr.ctx)
   433  	}
   434  
   435  	return nil
   436  }
   437  
   438  func calcSuperSchema(ctx context.Context, wr *doltdb.RootValue, tblName string) (*schema.SuperSchema, error) {
   439  	ss, found, err := wr.GetSuperSchema(ctx, tblName)
   440  
   441  	if err != nil {
   442  		return nil, err
   443  	} else if !found {
   444  		return nil, doltdb.ErrTableNotFound
   445  	}
   446  
   447  	return ss, nil
   448  }