diff --git a/go/cmd/dolt/commands/utils.go b/go/cmd/dolt/commands/utils.go index 7eeb79a8877..8c3c47e19b5 100644 --- a/go/cmd/dolt/commands/utils.go +++ b/go/cmd/dolt/commands/utils.go @@ -711,14 +711,30 @@ func getCommitInfoWithOptions(queryist cli.Queryist, sqlCtx *sql.Context, ref st return nil, fmt.Errorf("error parsing timestamp '%s': %v", row[3], err) } message := row[4].(string) - parent := row[5].(string) - height := uint64(len(rows)) + + var commitOrder uint64 + switch v := row[5].(type) { + case uint64: + commitOrder = v + case string: + var err error + commitOrder, err = strconv.ParseUint(v, 10, 64) + if err != nil { + return nil, fmt.Errorf("error parsing commit_order '%s': %v", v, err) + } + default: + return nil, fmt.Errorf("unexpected type for commit_order: %T", v) + } + + parent := row[6].(string) + height := commitOrder isHead := commitHash == hashOfHead var signature string - if len(row) > 7 { - signature = row[7].(string) + if opts.showSignature { + // Signature is always the last column when present + signature = row[len(row)-1].(string) } localBranchesForHash, err := getBranchesForHash(queryist, sqlCtx, commitHash, true) diff --git a/go/libraries/doltcore/sqle/dtablefunctions/dolt_log_table_function.go b/go/libraries/doltcore/sqle/dtablefunctions/dolt_log_table_function.go index f9d01a542da..df52aab797a 100644 --- a/go/libraries/doltcore/sqle/dtablefunctions/dolt_log_table_function.go +++ b/go/libraries/doltcore/sqle/dtablefunctions/dolt_log_table_function.go @@ -60,6 +60,7 @@ var logTableSchema = sql.Schema{ &sql.Column{Name: "email", Type: types.Text}, &sql.Column{Name: "date", Type: types.Datetime}, &sql.Column{Name: "message", Type: types.Text}, + &sql.Column{Name: "commit_order", Type: types.Uint64}, } // NewInstance creates a new instance of TableFunction interface @@ -764,7 +765,12 @@ func (itr *logTableFunctionRowIter) Next(ctx *sql.Context) (sql.Row, error) { return nil, err } - row := sql.NewRow(commitHash.String(), meta.Name, meta.Email, meta.Time(), meta.Description) + height, err := commit.Height() + if err != nil { + return nil, err + } + + row := sql.NewRow(commitHash.String(), meta.Name, meta.Email, meta.Time(), meta.Description, height) if itr.showParents { prStr, err := getParentsString(ctx, commit) diff --git a/go/libraries/doltcore/sqle/dtables/log_table.go b/go/libraries/doltcore/sqle/dtables/log_table.go index 4d5bbb137be..009c12cf9e8 100644 --- a/go/libraries/doltcore/sqle/dtables/log_table.go +++ b/go/libraries/doltcore/sqle/dtables/log_table.go @@ -92,6 +92,7 @@ func (dt *LogTable) Schema() sql.Schema { {Name: "email", Type: types.Text, Source: dt.tableName, PrimaryKey: false, DatabaseSource: dt.dbName}, {Name: "date", Type: types.Datetime, Source: dt.tableName, PrimaryKey: false, DatabaseSource: dt.dbName}, {Name: "message", Type: types.Text, Source: dt.tableName, PrimaryKey: false, DatabaseSource: dt.dbName}, + {Name: "commit_order", Type: types.Uint64, Source: dt.tableName, PrimaryKey: false, DatabaseSource: dt.dbName}, } } @@ -109,7 +110,11 @@ func (dt *LogTable) Partitions(*sql.Context) (sql.PartitionIter, error) { func (dt *LogTable) PartitionRows(ctx *sql.Context, p sql.Partition) (sql.RowIter, error) { switch p := p.(type) { case *doltdb.CommitPart: - return sql.RowsToRowIter(sql.NewRow(p.Hash().String(), p.Meta().Name, p.Meta().Email, p.Meta().Time(), p.Meta().Description)), nil + height, err := p.Commit().Height() + if err != nil { + return nil, err + } + return sql.RowsToRowIter(sql.NewRow(p.Hash().String(), p.Meta().Name, p.Meta().Email, p.Meta().Time(), p.Meta().Description, height)), nil default: return NewLogItr(ctx, dt.ddb, dt.head) } @@ -246,7 +251,12 @@ func (itr *LogItr) Next(ctx *sql.Context) (sql.Row, error) { return nil, err } - return sql.NewRow(h.String(), meta.Name, meta.Email, meta.Time(), meta.Description), nil + height, err := cm.Height() + if err != nil { + return nil, err + } + + return sql.NewRow(h.String(), meta.Name, meta.Email, meta.Time(), meta.Description, height), nil } // Close closes the iterator. diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go index 85d7626bdbf..22a5a77f117 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go @@ -4786,6 +4786,65 @@ var LogTableFunctionScriptTests = []queries.ScriptTest{ }, }, }, + { + Name: "commit_order column in dolt_log system table and function", + SetUpScript: []string{ + "create table t (pk int primary key);", + "call dolt_add('.')", + "set @Commit1 = '';", + "call dolt_commit_hash_out(@Commit1, '-am', 'commit 1');", + "call dolt_commit('--allow-empty', '-m', 'commit 2');", + "call dolt_checkout('-b', 'feature');", + "call dolt_commit('--allow-empty', '-m', 'feature commit');", + "call dolt_checkout('main');", + "call dolt_commit('--allow-empty', '-m', 'main commit');", + "call dolt_merge('feature', '-m', 'merge feature');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "describe dolt_log;", + Expected: []sql.Row{ + {"commit_hash", "text", "NO", "PRI", nil, ""}, + {"committer", "text", "NO", "", nil, ""}, + {"email", "text", "NO", "", nil, ""}, + {"date", "datetime", "NO", "", nil, ""}, + {"message", "text", "NO", "", nil, ""}, + {"commit_order", "bigint unsigned", "NO", "", nil, ""}, + }, + }, + { + Query: "select commit_order from dolt_log where message = 'commit 1';", + Expected: []sql.Row{ + {uint64(2)}, + }, + }, + { + Query: "select commit_order from dolt_log() where message = 'commit 1';", + Expected: []sql.Row{ + {uint64(2)}, + }, + }, + { + Query: "select (select commit_order from dolt_log where message = 'commit 1') = (select commit_order from dolt_log() where message = 'commit 1') as same_order;", + Expected: []sql.Row{ + {true}, + }, + }, + { + Query: "select count(distinct commit_order) = 5 as has_five_orders from dolt_log;", + Expected: []sql.Row{ + {true}, + }, + }, + // Test that commits on parallel branches can have the same commit_order (height) + { + Query: "select count(*) from (select commit_order, count(*) as cnt from dolt_log group by commit_order having cnt > 1) as duplicate_orders;", + Expected: []sql.Row{ + {1}, // feature and main commits should have same order + }, + }, + }, + }, } var LargeJsonObjectScriptTests = []queries.ScriptTest{ diff --git a/go/libraries/doltcore/sqle/sqlselect_test.go b/go/libraries/doltcore/sqle/sqlselect_test.go index 68b818bf255..26dba3a6991 100644 --- a/go/libraries/doltcore/sqle/sqlselect_test.go +++ b/go/libraries/doltcore/sqle/sqlselect_test.go @@ -753,6 +753,7 @@ func BasicSelectTests() []SelectTest { "bigbillieb@fake.horse", time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC).In(LoadedLocalLocation()), "Initialize data repository", + uint64(1), // commit_order for the initial commit }, }, ExpectedSqlSchema: sql.Schema{ @@ -761,6 +762,7 @@ func BasicSelectTests() []SelectTest { &sql.Column{Name: "email", Type: gmstypes.Text}, &sql.Column{Name: "date", Type: gmstypes.Datetime}, &sql.Column{Name: "message", Type: gmstypes.Text}, + &sql.Column{Name: "commit_order", Type: gmstypes.Uint64}, }, }, { diff --git a/integration-tests/bats/system-tables.bats b/integration-tests/bats/system-tables.bats index 5ed19accf29..af0c88ca2c0 100644 --- a/integration-tests/bats/system-tables.bats +++ b/integration-tests/bats/system-tables.bats @@ -67,6 +67,86 @@ teardown() { [[ ! "$output" =~ "Added test table" ]] || false } +@test "system-tables: dolt_log system table includes commit_order column" { + dolt sql -q "create table test (pk int, c1 int, primary key(pk))" + dolt add test + dolt commit -m "Added test table" + dolt commit --allow-empty -m "Empty commit" + + # Test that dolt_log system table has commit_order column + run dolt sql -q "describe dolt_log" + [ $status -eq 0 ] + [[ "$output" =~ "commit_order" ]] || false + + # Test that commit_order values are present and numeric + run dolt sql -q "select commit_order from dolt_log where message = 'Added test table'" + [ $status -eq 0 ] + [[ "$output" =~ [0-9]+ ]] || false + + # Test that we have 3 distinct commit orders (init commit + 2 new commits) + run dolt sql -q "select count(distinct commit_order) from dolt_log" + [ $status -eq 0 ] + [[ "$output" =~ "3" ]] || false +} + +@test "system-tables: dolt_log() table function includes commit_order column" { + dolt sql -q "create table test2 (pk int, c1 int, primary key(pk))" + dolt add test2 + dolt commit -m "Added test2 table" + + # Test that dolt_log() function has commit_order column + run dolt sql -q "select commit_order from dolt_log() where message = 'Added test2 table'" + [ $status -eq 0 ] + [[ "$output" =~ [0-9]+ ]] || false + + # Test that the function and system table return the same commit_order for the same commit + run dolt sql -q "select (select commit_order from dolt_log where message = 'Added test2 table') = (select commit_order from dolt_log() where message = 'Added test2 table') as orders_match" + [ $status -eq 0 ] + [[ "$output" =~ "true" ]] || false +} + +@test "system-tables: commit_order reflects topological order for branches" { + if [ "$SQL_ENGINE" = "remote-engine" ]; then + skip "needs checkout which is unsupported for remote-engine" + fi + + # Create initial commit + dolt sql -q "create table test (pk int, c1 int, primary key(pk))" + dolt add test + dolt commit -m "initial commit" + + # Create a branch + dolt checkout -b feature + dolt commit --allow-empty -m "feature commit" + + # Go back to main and make another commit + dolt checkout main + dolt commit --allow-empty -m "main commit" + + # Both feature and main commits should have the same commit_order (height) + # since they branched from the same parent + # Get the commit hashes and compare their heights using dolt_log() function + run dolt sql -q "select commit_hash from dolt_commits where message = 'feature commit'" + [ $status -eq 0 ] + feature_hash=$(echo "$output" | tail -n 1 | tr -d ' |') + + run dolt sql -q "select commit_hash from dolt_commits where message = 'main commit'" + [ $status -eq 0 ] + main_hash=$(echo "$output" | tail -n 1 | tr -d ' |') + + run dolt sql -q "select (select commit_order from dolt_log('$feature_hash') limit 1) = (select commit_order from dolt_log('$main_hash') limit 1) as same_height" + [ $status -eq 0 ] + [[ "$output" =~ "true" ]] || false + + # Merge feature into main + dolt merge feature -m "merge feature" + + # The merge commit should have a higher commit_order than both branch commits + run dolt sql -q "select (select commit_order from dolt_log where message = 'merge feature') > (select commit_order from dolt_log where message = 'main commit') as merge_higher" + [ $status -eq 0 ] + [[ "$output" =~ "true" ]] || false +} + @test "system-tables: query dolt_branches system table" { dolt checkout -b create-table-branch dolt sql -q "create table test (pk int, c1 int, primary key(pk))" diff --git a/integration-tests/mysql-client-tests/node/workbenchTests/logs.js b/integration-tests/mysql-client-tests/node/workbenchTests/logs.js index 72283645d84..3b7aa16b534 100644 --- a/integration-tests/mysql-client-tests/node/workbenchTests/logs.js +++ b/integration-tests/mysql-client-tests/node/workbenchTests/logs.js @@ -11,6 +11,7 @@ export const logTests = [ email: "mysql-test-runner@liquidata.co", date: "", message: "Initialize data repository", + commit_order: 1, parents: [], }, ], @@ -26,6 +27,7 @@ export const logTests = [ email: "dolt@dolthub.com", date: "", message: "Create table test", + commit_order: 2, }, { commit_hash: "", @@ -33,6 +35,7 @@ export const logTests = [ email: "mysql-test-runner@liquidata.co", date: "", message: "Initialize data repository", + commit_order: 1, }, ], matcher: logsMatcher, @@ -47,6 +50,7 @@ export const logTests = [ email: "dolt@dolthub.com", date: "", message: "Create table test", + commit_order: 2, parents: [""], }, ], diff --git a/integration-tests/mysql-client-tests/node/workbenchTests/matchers.js b/integration-tests/mysql-client-tests/node/workbenchTests/matchers.js index 132eed21653..f3b079c4f13 100644 --- a/integration-tests/mysql-client-tests/node/workbenchTests/matchers.js +++ b/integration-tests/mysql-client-tests/node/workbenchTests/matchers.js @@ -61,7 +61,7 @@ export function branchesMatcher(rows, exp) { } export function logsMatcher(rows, exp) { - const exceptionKeys = ["commit_hash", "date", "parents"]; + const exceptionKeys = ["commit_hash", "date", "parents", "commit_order"]; function getExceptionIsValid(row, key, expRow) { const val = row[key]; @@ -75,6 +75,8 @@ export function logsMatcher(rows, exp) { val.split(", ").filter((v) => !!v.length).length === expRow.parents.length ); + case "commit_order": + return typeof val === "number" && val > 0; default: return false; } diff --git a/integration-tests/mysql-client-tests/node/workbenchTests/merge.js b/integration-tests/mysql-client-tests/node/workbenchTests/merge.js index a9e4af5e481..21995be0623 100644 --- a/integration-tests/mysql-client-tests/node/workbenchTests/merge.js +++ b/integration-tests/mysql-client-tests/node/workbenchTests/merge.js @@ -27,6 +27,7 @@ export const mergeTests = [ committer: "dolt", email: "dolt@%", date: "", + commit_order: 3, parents: ["", ""], }, { @@ -35,6 +36,7 @@ export const mergeTests = [ committer: "Dolt", email: "dolt@dolthub.com", date: "", + commit_order: 2, parents: [""], }, { @@ -43,6 +45,7 @@ export const mergeTests = [ committer: "mysql-test-runner", email: "mysql-test-runner@liquidata.co", date: "", + commit_order: 1, parents: [], }, ],