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 }