github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/dolt_log_table_function.go (about) 1 // Copyright 2022 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 sqle 16 17 import ( 18 "fmt" 19 "strings" 20 21 "github.com/dolthub/go-mysql-server/sql" 22 "github.com/dolthub/go-mysql-server/sql/types" 23 "gopkg.in/src-d/go-errors.v1" 24 25 "github.com/dolthub/dolt/go/cmd/dolt/cli" 26 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" 27 "github.com/dolthub/dolt/go/libraries/doltcore/env/actions/commitwalk" 28 "github.com/dolthub/dolt/go/libraries/doltcore/merge" 29 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 30 "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" 31 "github.com/dolthub/dolt/go/store/hash" 32 ) 33 34 const logTableDefaultRowCount = 10 35 36 var _ sql.TableFunction = (*LogTableFunction)(nil) 37 var _ sql.ExecSourceRel = (*LogTableFunction)(nil) 38 39 type LogTableFunction struct { 40 ctx *sql.Context 41 42 revisionExprs []sql.Expression 43 notRevisionExprs []sql.Expression 44 notRevisionStrs []string 45 tableNames []string 46 47 minParents int 48 showParents bool 49 decoration string 50 51 database sql.Database 52 } 53 54 var logTableSchema = sql.Schema{ 55 &sql.Column{Name: "commit_hash", Type: types.Text}, 56 &sql.Column{Name: "committer", Type: types.Text}, 57 &sql.Column{Name: "email", Type: types.Text}, 58 &sql.Column{Name: "date", Type: types.Datetime}, 59 &sql.Column{Name: "message", Type: types.Text}, 60 } 61 62 // NewInstance creates a new instance of TableFunction interface 63 func (ltf *LogTableFunction) NewInstance(ctx *sql.Context, db sql.Database, expressions []sql.Expression) (sql.Node, error) { 64 newInstance := &LogTableFunction{ 65 ctx: ctx, 66 database: db, 67 } 68 69 node, err := newInstance.evalArguments(expressions...) 70 if err != nil { 71 return nil, err 72 } 73 74 return node, nil 75 } 76 77 // Database implements the sql.Databaser interface 78 func (ltf *LogTableFunction) Database() sql.Database { 79 return ltf.database 80 } 81 82 func (ltf *LogTableFunction) DataLength(ctx *sql.Context) (uint64, error) { 83 numBytesPerRow := schema.SchemaAvgLength(ltf.Schema()) 84 numRows, _, err := ltf.RowCount(ctx) 85 if err != nil { 86 return 0, err 87 } 88 return numBytesPerRow * numRows, nil 89 } 90 91 func (ltf *LogTableFunction) RowCount(_ *sql.Context) (uint64, bool, error) { 92 return logTableDefaultRowCount, false, nil 93 } 94 95 // WithDatabase implements the sql.Databaser interface 96 func (ltf *LogTableFunction) WithDatabase(database sql.Database) (sql.Node, error) { 97 nltf := *ltf 98 nltf.database = database 99 return &nltf, nil 100 } 101 102 // Name implements the sql.TableFunction interface 103 func (ltf *LogTableFunction) Name() string { 104 return "dolt_log" 105 } 106 107 // Resolved implements the sql.Resolvable interface 108 func (ltf *LogTableFunction) Resolved() bool { 109 for _, expr := range ltf.revisionExprs { 110 return expr.Resolved() 111 } 112 return true 113 } 114 115 func (ltf *LogTableFunction) IsReadOnly() bool { 116 return true 117 } 118 119 // String implements the Stringer interface 120 func (ltf *LogTableFunction) String() string { 121 return fmt.Sprintf("DOLT_LOG(%s)", ltf.getOptionsString()) 122 } 123 124 func (ltf *LogTableFunction) getOptionsString() string { 125 var options []string 126 127 for _, expr := range ltf.revisionExprs { 128 options = append(options, expr.String()) 129 } 130 131 for _, expr := range ltf.notRevisionStrs { 132 options = append(options, fmt.Sprintf("^%s", expr)) 133 } 134 135 if ltf.minParents > 0 { 136 options = append(options, fmt.Sprintf("--%s %d", cli.MinParentsFlag, ltf.minParents)) 137 } 138 139 if ltf.showParents { 140 options = append(options, fmt.Sprintf("--%s", cli.ParentsFlag)) 141 } 142 143 if len(ltf.decoration) > 0 && ltf.decoration != "auto" { 144 options = append(options, fmt.Sprintf("--%s %s", cli.DecorateFlag, ltf.decoration)) 145 } 146 147 if len(ltf.tableNames) > 0 { 148 options = append(options, "--tables", strings.Join(ltf.tableNames, ",")) 149 } 150 151 return strings.Join(options, ", ") 152 } 153 154 // Schema implements the sql.Node interface. 155 func (ltf *LogTableFunction) Schema() sql.Schema { 156 logSchema := logTableSchema 157 158 if ltf.showParents { 159 logSchema = append(logSchema, &sql.Column{Name: "parents", Type: types.Text}) 160 } 161 if shouldDecorateWithRefs(ltf.decoration) { 162 logSchema = append(logSchema, &sql.Column{Name: "refs", Type: types.Text}) 163 } 164 165 return logSchema 166 } 167 168 // Children implements the sql.Node interface. 169 func (ltf *LogTableFunction) Children() []sql.Node { 170 return nil 171 } 172 173 // WithChildren implements the sql.Node interface. 174 func (ltf *LogTableFunction) WithChildren(children ...sql.Node) (sql.Node, error) { 175 if len(children) != 0 { 176 return nil, fmt.Errorf("unexpected children") 177 } 178 return ltf, nil 179 } 180 181 // CheckPrivileges implements the interface sql.Node. 182 func (ltf *LogTableFunction) CheckPrivileges(ctx *sql.Context, opChecker sql.PrivilegedOperationChecker) bool { 183 tblNames, err := ltf.database.GetTableNames(ctx) 184 if err != nil { 185 return false 186 } 187 188 var operations []sql.PrivilegedOperation 189 for _, tblName := range tblNames { 190 subject := sql.PrivilegeCheckSubject{Database: ltf.database.Name(), Table: tblName} 191 operations = append(operations, sql.NewPrivilegedOperation(subject, sql.PrivilegeType_Select)) 192 } 193 194 return opChecker.UserHasPrivileges(ctx, operations...) 195 } 196 197 // Expressions implements the sql.Expressioner interface. 198 func (ltf *LogTableFunction) Expressions() []sql.Expression { 199 return []sql.Expression{} 200 } 201 202 // getDoltArgs builds an argument string from sql expressions so that we can 203 // later parse the arguments with the same util as the CLI 204 func getDoltArgs(ctx *sql.Context, expressions []sql.Expression, name string) ([]string, error) { 205 var args []string 206 207 for _, expr := range expressions { 208 childVal, err := expr.Eval(ctx, nil) 209 if err != nil { 210 return nil, err 211 } 212 213 if !types.IsText(expr.Type()) { 214 return args, sql.ErrInvalidArgumentDetails.New(name, expr.String()) 215 } 216 217 text, _, err := types.Text.Convert(childVal) 218 if err != nil { 219 return nil, err 220 } 221 222 if text != nil { 223 args = append(args, text.(string)) 224 } 225 } 226 227 return args, nil 228 } 229 230 func (ltf *LogTableFunction) addOptions(expression []sql.Expression) error { 231 args, err := getDoltArgs(ltf.ctx, expression, ltf.Name()) 232 if err != nil { 233 return err 234 } 235 236 apr, err := cli.CreateLogArgParser(true).Parse(args) 237 if err != nil { 238 return sql.ErrInvalidArgumentDetails.New(ltf.Name(), err.Error()) 239 } 240 241 if notRevisionStrs, ok := apr.GetValueList(cli.NotFlag); ok { 242 ltf.notRevisionStrs = append(ltf.notRevisionStrs, notRevisionStrs...) 243 } 244 245 if tableNames, ok := apr.GetValueList(cli.TablesFlag); ok { 246 ltf.tableNames = append(ltf.tableNames, tableNames...) 247 } 248 249 minParents := apr.GetIntOrDefault(cli.MinParentsFlag, 0) 250 if apr.Contains(cli.MergesFlag) { 251 minParents = 2 252 } 253 254 ltf.minParents = minParents 255 ltf.showParents = apr.Contains(cli.ParentsFlag) 256 257 decorateOption := apr.GetValueOrDefault(cli.DecorateFlag, "auto") 258 switch decorateOption { 259 case "short", "full", "auto", "no": 260 default: 261 return ltf.invalidArgDetailsErr(fmt.Sprintf("invalid --decorate option: %s", decorateOption)) 262 } 263 ltf.decoration = decorateOption 264 265 return nil 266 } 267 268 func (ltf *LogTableFunction) WithExpressions(exprs ...sql.Expression) (sql.Node, error) { 269 if len(exprs) != 0 { 270 return nil, sql.ErrInvalidChildrenNumber.New(0, len(exprs)) 271 } 272 return ltf, nil 273 } 274 275 // evalArguments converts the input expressions into string literals and 276 // formats them as function arguments. 277 func (ltf *LogTableFunction) evalArguments(expression ...sql.Expression) (sql.Node, error) { 278 for _, expr := range expression { 279 if !expr.Resolved() { 280 return nil, ErrInvalidNonLiteralArgument.New(ltf.Name(), expr.String()) 281 } 282 // prepared statements resolve functions beforehand, so above check fails 283 if _, ok := expr.(sql.FunctionExpression); ok { 284 return nil, ErrInvalidNonLiteralArgument.New(ltf.Name(), expr.String()) 285 } 286 } 287 288 newLtf := *ltf 289 if err := newLtf.addOptions(expression); err != nil { 290 return nil, err 291 } 292 293 // Gets revisions, excluding any flag-related expression 294 for i, ex := range expression { 295 if !strings.Contains(ex.String(), "--") && !(i > 0 && strings.Contains(expression[i-1].String(), "--")) { 296 exStr := strings.ReplaceAll(ex.String(), "'", "") 297 if strings.HasPrefix(exStr, "^") { 298 newLtf.notRevisionExprs = append(newLtf.notRevisionExprs, ex) 299 } else { 300 newLtf.revisionExprs = append(newLtf.revisionExprs, ex) 301 } 302 } 303 } 304 305 if err := newLtf.validateRevisionExpressions(); err != nil { 306 return nil, err 307 } 308 309 return &newLtf, nil 310 } 311 312 func (ltf *LogTableFunction) validateRevisionExpressions() error { 313 // We must convert the expressions to strings before making string comparisons 314 // For dolt_log('^main'), ltf.revisionExpr.String() = "'^main'"" and revisionStr = "^main" 315 316 revisionStrs, err := mustExpressionsToString(ltf.ctx, ltf.revisionExprs) 317 if err != nil { 318 return err 319 } 320 notRevisionStrs, err := mustExpressionsToString(ltf.ctx, ltf.notRevisionExprs) 321 if err != nil { 322 return err 323 } 324 325 for i, revisionStr := range revisionStrs { 326 if !types.IsText(ltf.revisionExprs[i].Type()) { 327 return ltf.invalidArgDetailsErr(ltf.revisionExprs[i].String()) 328 } 329 if strings.Contains(revisionStr, "..") && (len(revisionStrs) > 1 || ltf.notRevisionExprs != nil || ltf.notRevisionStrs != nil) { 330 return ltf.invalidArgDetailsErr("revision cannot contain '..' or '...' if multiple revisions exist") 331 } 332 } 333 334 for i, notRevisionStr := range notRevisionStrs { 335 if !types.IsText(ltf.notRevisionExprs[i].Type()) { 336 return ltf.invalidArgDetailsErr(ltf.notRevisionExprs[i].String()) 337 } 338 if strings.Contains(notRevisionStr, "..") { 339 return ltf.invalidArgDetailsErr("revision cannot contain both '..' or '...' and '^'") 340 } 341 } 342 for _, notRevStr := range ltf.notRevisionStrs { 343 if strings.Contains(notRevStr, "..") { 344 return ltf.invalidArgDetailsErr("--not revision cannot contain '..'") 345 } 346 if strings.HasPrefix(notRevStr, "^") { 347 return ltf.invalidArgDetailsErr("--not revision cannot contain '^'") 348 } 349 } 350 351 return nil 352 } 353 354 // mustExpressionsToString converts a slice of expressions to a slice of resolved strings. 355 func mustExpressionsToString(ctx *sql.Context, expr []sql.Expression) ([]string, error) { 356 var valStrs []string 357 358 for _, ex := range expr { 359 valStr, err := expressionToString(ctx, ex) 360 if err != nil { 361 return nil, err 362 } 363 364 valStrs = append(valStrs, valStr) 365 } 366 367 return valStrs, nil 368 } 369 370 func expressionToString(ctx *sql.Context, expr sql.Expression) (string, error) { 371 val, err := expr.Eval(ctx, nil) 372 if err != nil { 373 return "", err 374 } 375 376 valStr, ok := val.(string) 377 if !ok { 378 return "", fmt.Errorf("received '%v' when expecting string", val) 379 } 380 381 return valStr, nil 382 } 383 384 func (ltf *LogTableFunction) invalidArgDetailsErr(reason string) *errors.Error { 385 return sql.ErrInvalidArgumentDetails.New(ltf.Name(), reason) 386 } 387 388 // RowIter implements the sql.Node interface 389 func (ltf *LogTableFunction) RowIter(ctx *sql.Context, row sql.Row) (sql.RowIter, error) { 390 revisionValStrs, notRevisionValStrs, threeDot, err := ltf.evaluateArguments() 391 if err != nil { 392 return nil, err 393 } 394 notRevisionValStrs = append(notRevisionValStrs, ltf.notRevisionStrs...) 395 396 sqledb, ok := ltf.database.(dsess.SqlDatabase) 397 if !ok { 398 return nil, fmt.Errorf("unexpected database type: %T", ltf.database) 399 } 400 401 sess := dsess.DSessFromSess(ctx.Session) 402 var commit *doltdb.Commit 403 404 matchFunc := func(optCmt *doltdb.OptionalCommit) (bool, error) { 405 commit, ok := optCmt.ToCommit() 406 if !ok { 407 return false, nil 408 } 409 410 return commit.NumParents() >= ltf.minParents, nil 411 } 412 413 cHashToRefs, err := getCommitHashToRefs(ctx, sqledb.DbData().Ddb, ltf.decoration) 414 if err != nil { 415 return nil, err 416 } 417 418 var commits []*doltdb.Commit 419 if len(revisionValStrs) == 0 { 420 // If no revisions given, use session head 421 commit, err = sess.GetHeadCommit(ctx, sqledb.RevisionQualifiedName()) 422 if err != nil { 423 return nil, err 424 } 425 commits = append(commits, commit) 426 } 427 428 dbName := sess.Session.GetCurrentDatabase() 429 headRef, err := sess.CWBHeadRef(ctx, dbName) 430 if err != nil { 431 return nil, err 432 } 433 434 for _, revisionStr := range revisionValStrs { 435 cs, err := doltdb.NewCommitSpec(revisionStr) 436 if err != nil { 437 return nil, err 438 } 439 440 optCmt, err := sqledb.DbData().Ddb.Resolve(ctx, cs, headRef) 441 if err != nil { 442 return nil, err 443 } 444 commit, ok = optCmt.ToCommit() 445 if err != nil { 446 return nil, doltdb.ErrGhostCommitEncountered 447 } 448 449 commits = append(commits, commit) 450 } 451 452 var notCommits []*doltdb.Commit 453 for _, notRevisionStr := range notRevisionValStrs { 454 cs, err := doltdb.NewCommitSpec(notRevisionStr) 455 if err != nil { 456 return nil, err 457 } 458 459 optCmt, err := sqledb.DbData().Ddb.Resolve(ctx, cs, headRef) 460 if err != nil { 461 return nil, err 462 } 463 notCommit, ok := optCmt.ToCommit() 464 if !ok { 465 return nil, doltdb.ErrGhostCommitEncountered 466 } 467 468 notCommits = append(notCommits, notCommit) 469 } 470 471 if threeDot { 472 mergeBase, err := merge.MergeBase(ctx, commits[0], commits[1]) 473 if err != nil { 474 return nil, err 475 } 476 477 mergeCs, err := doltdb.NewCommitSpec(mergeBase.String()) 478 if err != nil { 479 return nil, err 480 } 481 482 // Use merge base as excluding commit 483 optCmt, err := sqledb.DbData().Ddb.Resolve(ctx, mergeCs, nil) 484 if err != nil { 485 return nil, err 486 } 487 mergeCommit, ok := optCmt.ToCommit() 488 if !ok { 489 return nil, doltdb.ErrGhostCommitEncountered 490 } 491 492 notCommits = append(notCommits, mergeCommit) 493 494 return ltf.NewDotDotLogTableFunctionRowIter(ctx, sqledb.DbData().Ddb, commits, notCommits, matchFunc, cHashToRefs, ltf.tableNames) 495 } 496 497 if len(revisionValStrs) <= 1 && len(notRevisionValStrs) == 0 { 498 return ltf.NewLogTableFunctionRowIter(ctx, sqledb.DbData().Ddb, commits[0], matchFunc, cHashToRefs, ltf.tableNames) 499 } 500 501 return ltf.NewDotDotLogTableFunctionRowIter(ctx, sqledb.DbData().Ddb, commits, notCommits, matchFunc, cHashToRefs, ltf.tableNames) 502 } 503 504 // evaluateArguments returns revisionValStrs, notRevisionValStrs, and three dot boolean. 505 // It evaluates the argument expressions to turn them into values this LogTableFunction 506 // can use. Note that this method only evals the expressions, and doesn't validate the values. 507 func (ltf *LogTableFunction) evaluateArguments() (revisionValStrs []string, notRevisionValStrs []string, threeDot bool, err error) { 508 for _, expr := range ltf.revisionExprs { 509 valStr, err := expressionToString(ltf.ctx, expr) 510 if err != nil { 511 return nil, nil, false, err 512 } 513 514 if strings.Contains(valStr, "..") { 515 if strings.Contains(valStr, "...") { 516 refs := strings.Split(valStr, "...") 517 return refs, nil, true, nil 518 } 519 refs := strings.Split(valStr, "..") 520 return []string{refs[1]}, []string{refs[0]}, false, nil 521 } 522 523 revisionValStrs = append(revisionValStrs, valStr) 524 } 525 526 for _, notExpr := range ltf.notRevisionExprs { 527 notValStr, err := expressionToString(ltf.ctx, notExpr) 528 if err != nil { 529 return nil, nil, false, err 530 } 531 532 if strings.HasPrefix(notValStr, "^") { 533 notValStr = strings.TrimPrefix(notValStr, "^") 534 } 535 536 notRevisionValStrs = append(notRevisionValStrs, notValStr) 537 } 538 539 return revisionValStrs, notRevisionValStrs, false, nil 540 } 541 542 func getCommitHashToRefs(ctx *sql.Context, ddb *doltdb.DoltDB, decoration string) (map[hash.Hash][]string, error) { 543 cHashToRefs := map[hash.Hash][]string{} 544 545 // Get all branches 546 branches, err := ddb.GetBranchesWithHashes(ctx) 547 if err != nil { 548 return nil, err 549 } 550 for _, b := range branches { 551 refName := b.Ref.String() 552 if decoration != "full" { 553 refName = b.Ref.GetPath() // trim out "refs/heads/" 554 } 555 cHashToRefs[b.Hash] = append(cHashToRefs[b.Hash], refName) 556 } 557 558 // Get all remote branches 559 remotes, err := ddb.GetRemotesWithHashes(ctx) 560 if err != nil { 561 return nil, err 562 } 563 for _, r := range remotes { 564 refName := r.Ref.String() 565 if decoration != "full" { 566 refName = r.Ref.GetPath() // trim out "refs/remotes/" 567 } 568 cHashToRefs[r.Hash] = append(cHashToRefs[r.Hash], refName) 569 } 570 571 // Get all tags 572 tags, err := ddb.GetTagsWithHashes(ctx) 573 if err != nil { 574 return nil, err 575 } 576 for _, t := range tags { 577 tagName := t.Tag.GetDoltRef().String() 578 if decoration != "full" { 579 tagName = t.Tag.Name // trim out "refs/tags/" 580 } 581 tagName = fmt.Sprintf("tag: %s", tagName) 582 cHashToRefs[t.Hash] = append(cHashToRefs[t.Hash], tagName) 583 } 584 585 return cHashToRefs, nil 586 } 587 588 //------------------------------------ 589 // logTableFunctionRowIter 590 //------------------------------------ 591 592 var _ sql.RowIter = (*logTableFunctionRowIter)(nil) 593 594 // logTableFunctionRowIter is a sql.RowIter implementation which iterates over each commit as if it's a row in the table. 595 type logTableFunctionRowIter struct { 596 child doltdb.CommitItr 597 showParents bool 598 decoration string 599 cHashToRefs map[hash.Hash][]string 600 headHash hash.Hash 601 602 tableNames []string 603 } 604 605 func (ltf *LogTableFunction) NewLogTableFunctionRowIter(ctx *sql.Context, ddb *doltdb.DoltDB, commit *doltdb.Commit, matchFn func(*doltdb.OptionalCommit) (bool, error), cHashToRefs map[hash.Hash][]string, tableNames []string) (*logTableFunctionRowIter, error) { 606 h, err := commit.HashOf() 607 if err != nil { 608 return nil, err 609 } 610 611 child, err := commitwalk.GetTopologicalOrderIterator(ctx, ddb, []hash.Hash{h}, matchFn) 612 if err != nil { 613 return nil, err 614 } 615 616 return &logTableFunctionRowIter{ 617 child: child, 618 showParents: ltf.showParents, 619 decoration: ltf.decoration, 620 cHashToRefs: cHashToRefs, 621 headHash: h, 622 tableNames: tableNames, 623 }, nil 624 } 625 626 func (ltf *LogTableFunction) NewDotDotLogTableFunctionRowIter(ctx *sql.Context, ddb *doltdb.DoltDB, commits []*doltdb.Commit, excludingCommits []*doltdb.Commit, matchFn func(*doltdb.OptionalCommit) (bool, error), cHashToRefs map[hash.Hash][]string, tableNames []string) (*logTableFunctionRowIter, error) { 627 hashes := make([]hash.Hash, len(commits)) 628 for i, commit := range commits { 629 h, err := commit.HashOf() 630 if err != nil { 631 return nil, err 632 } 633 hashes[i] = h 634 } 635 636 exHashes := make([]hash.Hash, len(excludingCommits)) 637 for i, exCommit := range excludingCommits { 638 h, err := exCommit.HashOf() 639 if err != nil { 640 return nil, err 641 } 642 exHashes[i] = h 643 } 644 645 child, err := commitwalk.GetDotDotRevisionsIterator(ctx, ddb, hashes, ddb, exHashes, matchFn) 646 if err != nil { 647 return nil, err 648 } 649 650 var headHash hash.Hash 651 652 if len(hashes) == 1 { 653 headHash = hashes[0] 654 } 655 656 return &logTableFunctionRowIter{ 657 child: child, 658 showParents: ltf.showParents, 659 decoration: ltf.decoration, 660 cHashToRefs: cHashToRefs, 661 headHash: headHash, 662 tableNames: tableNames, 663 }, nil 664 } 665 666 // Next retrieves the next row. It will return io.EOF if it's the last row. 667 // After retrieving the last row, Close will be automatically closed. 668 func (itr *logTableFunctionRowIter) Next(ctx *sql.Context) (sql.Row, error) { 669 var commitHash hash.Hash 670 var commit *doltdb.Commit 671 var optCmt *doltdb.OptionalCommit 672 var err error 673 for { 674 commitHash, optCmt, err = itr.child.Next(ctx) 675 if err != nil { 676 return nil, err 677 } 678 ok := false 679 commit, ok = optCmt.ToCommit() 680 if !ok { 681 return nil, doltdb.ErrGhostCommitEncountered 682 } 683 684 if itr.tableNames != nil { 685 if commit.NumParents() == 0 { 686 // if we're at the root commit, we continue without checking if any tables changed 687 // we expect EOF to be returned on the next call to Next(), but continue in case there are more commits 688 continue 689 } 690 optCmt, err := commit.GetParent(ctx, 0) 691 if err != nil { 692 return nil, err 693 } 694 parent0Cm, ok := optCmt.ToCommit() 695 if !ok { 696 return nil, doltdb.ErrGhostCommitEncountered 697 } 698 699 var parent1Cm *doltdb.Commit 700 if commit.NumParents() > 1 { 701 optCmt, err = commit.GetParent(ctx, 1) 702 if err != nil { 703 return nil, err 704 } 705 parent1Cm, ok = optCmt.ToCommit() 706 if !ok { 707 return nil, doltdb.ErrGhostCommitEncountered 708 } 709 } 710 711 parent0RV, err := parent0Cm.GetRootValue(ctx) 712 if err != nil { 713 return nil, err 714 } 715 var parent1RV doltdb.RootValue 716 if parent1Cm != nil { 717 parent1RV, err = parent1Cm.GetRootValue(ctx) 718 if err != nil { 719 return nil, err 720 } 721 } 722 childRV, err := commit.GetRootValue(ctx) 723 if err != nil { 724 return nil, err 725 } 726 727 didChange := false 728 for _, tableName := range itr.tableNames { 729 didChange, err = didTableChangeBetweenRootValues(ctx, childRV, parent0RV, parent1RV, tableName) 730 if err != nil { 731 return nil, err 732 } 733 if didChange { 734 break 735 } 736 } 737 738 if didChange { 739 break 740 } 741 } else { 742 break 743 } 744 } 745 746 meta, err := commit.GetCommitMeta(ctx) 747 if err != nil { 748 return nil, err 749 } 750 751 row := sql.NewRow(commitHash.String(), meta.Name, meta.Email, meta.Time(), meta.Description) 752 753 if itr.showParents { 754 prStr, err := getParentsString(ctx, commit) 755 if err != nil { 756 return nil, err 757 } 758 row = row.Append(sql.NewRow(prStr)) 759 } 760 761 if shouldDecorateWithRefs(itr.decoration) { 762 branchNames := itr.cHashToRefs[commitHash] 763 isHead := itr.headHash == commitHash 764 row = row.Append(sql.NewRow(getRefsString(branchNames, isHead))) 765 } 766 767 return row, nil 768 } 769 770 func (itr *logTableFunctionRowIter) Close(_ *sql.Context) error { 771 return nil 772 } 773 774 func getRefsString(branchNames []string, isHead bool) string { 775 if len(branchNames) == 0 { 776 return "" 777 } 778 var refStr string 779 if isHead { 780 refStr += "HEAD -> " 781 } 782 refStr += strings.Join(branchNames, ", ") 783 784 return refStr 785 } 786 787 func getParentsString(ctx *sql.Context, cm *doltdb.Commit) (string, error) { 788 parents, err := cm.ParentHashes(ctx) 789 if err != nil { 790 return "", err 791 } 792 793 var prStr string 794 for i, h := range parents { 795 prStr += h.String() 796 if i < len(parents)-1 { 797 prStr += ", " 798 } 799 } 800 801 return prStr, nil 802 } 803 804 // Default ("auto") for the dolt_log table function is "no" 805 func shouldDecorateWithRefs(decoration string) bool { 806 return decoration == "full" || decoration == "short" 807 } 808 809 // didTableChangeBetweenRootValues checks if the given table changed between the two given root values. 810 func didTableChangeBetweenRootValues(ctx *sql.Context, child, parent0, parent1 doltdb.RootValue, tableName string) (bool, error) { 811 childHash, childOk, err := child.GetTableHash(ctx, tableName) 812 if err != nil { 813 return false, err 814 } 815 parent0Hash, parent0Ok, err := parent0.GetTableHash(ctx, tableName) 816 if err != nil { 817 return false, err 818 } 819 var parent1Hash hash.Hash 820 var parent1Ok bool 821 if parent1 != nil { 822 parent1Hash, parent1Ok, err = parent1.GetTableHash(ctx, tableName) 823 if err != nil { 824 return false, err 825 } 826 } 827 828 if parent1 == nil { 829 if !childOk && !parent0Ok { 830 return false, nil 831 } else if !childOk && parent0Ok { 832 return true, nil 833 } else if childOk && !parent0Ok { 834 return true, nil 835 } else { 836 return childHash != parent0Hash, nil 837 } 838 } else { 839 if !childOk && !parent0Ok && !parent1Ok { 840 return false, nil 841 } else if !childOk && parent0Ok && !parent1Ok { 842 return true, nil 843 } else if !childOk && !parent0Ok && parent1Ok { 844 return true, nil 845 } else if !childOk && parent0Ok && parent1Ok { 846 return true, nil 847 } else if childOk && !parent0Ok && !parent1Ok { 848 return true, nil 849 } else if childOk && !parent0Ok && parent1Ok { 850 return childHash != parent1Hash, nil 851 } else if childOk && parent0Ok && !parent1Ok { 852 return childHash != parent0Hash, nil 853 } else { 854 return childHash != parent0Hash || childHash != parent1Hash, nil 855 } 856 } 857 }