diff --git a/go/libraries/doltcore/doltdb/system_table.go b/go/libraries/doltcore/doltdb/system_table.go index 9701487a00..3ac76c9e34 100644 --- a/go/libraries/doltcore/doltdb/system_table.go +++ b/go/libraries/doltcore/doltdb/system_table.go @@ -162,6 +162,7 @@ func GeneratedSystemTableNames() []string { GetBranchActivityTableName(), // [dtables.StatusTable] now uses [adapters.DoltTableAdapterRegistry] in its constructor for Doltgres. StatusTableName, + StatusIgnoredTableName, } } @@ -435,6 +436,9 @@ const ( // StatusTableName is the status system table name. StatusTableName = "dolt_status" + // StatusIgnoredTableName is the status_ignored system table name. + StatusIgnoredTableName = "dolt_status_ignored" + // MergeStatusTableName is the merge status system table name. MergeStatusTableName = "dolt_merge_status" @@ -477,6 +481,7 @@ var DoltGeneratedTableNames = []string{ CommitsTableName, CommitAncestorsTableName, StatusTableName, + StatusIgnoredTableName, MergeStatusTableName, TagsTableName, } diff --git a/go/libraries/doltcore/sqle/database.go b/go/libraries/doltcore/sqle/database.go index c0af6f16d9..ccbf46751c 100644 --- a/go/libraries/doltcore/sqle/database.go +++ b/go/libraries/doltcore/sqle/database.go @@ -758,59 +758,24 @@ func (db Database) getTableInsensitiveWithRoot(ctx *sql.Context, head *doltdb.Co return nil, false, err } if !resolve.UseSearchPath || isDoltgresSystemTable { - sess := dsess.DSessFromSess(ctx.Session) - var rootsProvider env.RootsProvider[*sql.Context] - rootsProvider = dsess.NewSessionStateAdapter( - sess, db.RevisionQualifiedName(), - concurrentmap.New[string, env.Remote](), - concurrentmap.New[string, env.BranchConfig](), - concurrentmap.New[string, env.Remote]()) - ws, err := sess.WorkingSet(ctx, db.RevisionQualifiedName()) + rootsProvider, ws, err := getStatusTableRootsProvider(ctx, db, ds, asOf) if err != nil { return nil, false, err } - - asOf, ok := asOf.(string) - if !ok { - return nil, false, fmt.Errorf( - "unexpected type for asOf param: %T", asOf) - } - - // If |asOf| is set, then we need to get the correct roots for the - // status table to use. We skip the special revision spec, HEAD, - // because it represents the current branch. - if asOf != "" && !strings.EqualFold(asOf, "HEAD") { - // If |asOf| is a branch name, then grab the working set for that - // branch and use its data. - ddb, ok := ds.GetDoltDB(ctx, ctx.GetCurrentDatabase()) - if !ok { - return nil, false, fmt.Errorf( - "unable to get DoltDB for database %s", ctx.GetCurrentDatabase()) - } - - _, hasBranch, err := ddb.HasBranch(ctx, asOf) - if err != nil { - return nil, false, err - } - if hasBranch { - branchRoots, err := getRootsForBranch(ctx, ddb, asOf) - if err != nil { - return nil, false, err - } - rootsProvider = &staticRootsProvider{ - roots: branchRoots, - } - } else { - // If this isn't a branch, then it's a tag or a commit, or a - // commit spec. In all of these cases, dolt_status will have - // no data, because there is no valid head/working/staged roots, - // so we provide a nil rootsProvider - rootsProvider = nil - } - } - dt, found = dtables.NewStatusTable(ctx, lwrName, db.ddb, ws, rootsProvider), true } + case doltdb.StatusIgnoredTableName: + isDoltgresSystemTable, err := resolve.IsDoltgresSystemTable(ctx, tname, root) + if err != nil { + return nil, false, err + } + if !resolve.UseSearchPath || isDoltgresSystemTable { + rootsProvider, ws, err := getStatusTableRootsProvider(ctx, db, ds, asOf) + if err != nil { + return nil, false, err + } + dt, found = dtables.NewStatusIgnoredTable(ctx, lwrName, db.ddb, ws, rootsProvider), true + } case doltdb.MergeStatusTableName, doltdb.GetMergeStatusTableName(): isDoltgresSystemTable, err := resolve.IsDoltgresSystemTable(ctx, tname, root) if err != nil { @@ -1296,6 +1261,68 @@ func (srp *staticRootsProvider) GetRoots(ctx *sql.Context) (doltdb.Roots, error) var _ env.RootsProvider[*sql.Context] = (*staticRootsProvider)(nil) +// getStatusTableRootsProvider returns the roots provider and working set needed for status tables +// (dolt_status and dolt_status_ignored). This handles the asOf parameter to support querying status +// for different branches, tags, or commits. +func getStatusTableRootsProvider( + ctx *sql.Context, + db Database, + ds *dsess.DoltSession, + asOf interface{}, +) (env.RootsProvider[*sql.Context], *doltdb.WorkingSet, error) { + sess := dsess.DSessFromSess(ctx.Session) + var rootsProvider env.RootsProvider[*sql.Context] + rootsProvider = dsess.NewSessionStateAdapter( + sess, db.RevisionQualifiedName(), + concurrentmap.New[string, env.Remote](), + concurrentmap.New[string, env.BranchConfig](), + concurrentmap.New[string, env.Remote]()) + ws, err := sess.WorkingSet(ctx, db.RevisionQualifiedName()) + if err != nil { + return nil, nil, err + } + + asOfStr, ok := asOf.(string) + if !ok { + return nil, nil, fmt.Errorf("unexpected type for asOf param: %T", asOf) + } + + // If |asOf| is set, then we need to get the correct roots for the + // status table to use. We skip the special revision spec, HEAD, + // because it represents the current branch. + if asOfStr != "" && !strings.EqualFold(asOfStr, "HEAD") { + // If |asOf| is a branch name, then grab the working set for that + // branch and use its data. + ddb, ok := ds.GetDoltDB(ctx, ctx.GetCurrentDatabase()) + if !ok { + return nil, nil, fmt.Errorf( + "unable to get DoltDB for database %s", ctx.GetCurrentDatabase()) + } + + _, hasBranch, err := ddb.HasBranch(ctx, asOfStr) + if err != nil { + return nil, nil, err + } + if hasBranch { + branchRoots, err := getRootsForBranch(ctx, ddb, asOfStr) + if err != nil { + return nil, nil, err + } + rootsProvider = &staticRootsProvider{ + roots: branchRoots, + } + } else { + // If this isn't a branch, then it's a tag or a commit, or a + // commit spec. In all of these cases, the status table will have + // no data, because there is no valid head/working/staged roots, + // so we provide a nil rootsProvider + rootsProvider = nil + } + } + + return rootsProvider, ws, nil +} + // workingSetStagedRoot returns the staged root for the current session in the database // named |dbName|. If a working set is not available (e.g. if a commit or tag is checked // out), this function returns an ErrOperationNotSupportedInDetachedHead error. diff --git a/go/libraries/doltcore/sqle/dtables/status_ignored_table.go b/go/libraries/doltcore/sqle/dtables/status_ignored_table.go new file mode 100644 index 0000000000..402df8d980 --- /dev/null +++ b/go/libraries/doltcore/sqle/dtables/status_ignored_table.go @@ -0,0 +1,204 @@ +// Copyright 2026 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dtables + +import ( + "io" + + "github.com/dolthub/go-mysql-server/sql" + "github.com/dolthub/go-mysql-server/sql/types" + + "github.com/dolthub/dolt/go/libraries/doltcore/diff" + "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" + "github.com/dolthub/dolt/go/libraries/doltcore/env" + "github.com/dolthub/dolt/go/libraries/doltcore/schema" + "github.com/dolthub/dolt/go/libraries/doltcore/sqle/adapters" + "github.com/dolthub/dolt/go/libraries/doltcore/sqle/index" +) + +const statusIgnoredDefaultRowCount = 10 + +// StatusIgnoredTable is a sql.Table implementation that shows status including ignored tables. +// This is the SQL equivalent of `dolt status --ignored`. +type StatusIgnoredTable struct { + rootsProvider env.RootsProvider[*sql.Context] + ddb *doltdb.DoltDB + workingSet *doltdb.WorkingSet + tableName string +} + +var _ sql.StatisticsTable = (*StatusIgnoredTable)(nil) + +func (st StatusIgnoredTable) DataLength(ctx *sql.Context) (uint64, error) { + numBytesPerRow := schema.SchemaAvgLength(st.Schema()) + numRows, _, err := st.RowCount(ctx) + if err != nil { + return 0, err + } + return numBytesPerRow * numRows, nil +} + +func (st StatusIgnoredTable) RowCount(_ *sql.Context) (uint64, bool, error) { + return statusIgnoredDefaultRowCount, false, nil +} + +func (st StatusIgnoredTable) Name() string { + return st.tableName +} + +func (st StatusIgnoredTable) String() string { + return st.tableName +} + +func (st StatusIgnoredTable) Schema() sql.Schema { + return []*sql.Column{ + {Name: "table_name", Type: types.Text, Source: doltdb.StatusIgnoredTableName, PrimaryKey: true, Nullable: false}, + {Name: "staged", Type: types.Boolean, Source: doltdb.StatusIgnoredTableName, PrimaryKey: true, Nullable: false}, + {Name: "status", Type: types.Text, Source: doltdb.StatusIgnoredTableName, PrimaryKey: true, Nullable: false}, + {Name: "ignored", Type: types.Boolean, Source: doltdb.StatusIgnoredTableName, PrimaryKey: false, Nullable: false}, + } +} + +func (st StatusIgnoredTable) Collation() sql.CollationID { + return sql.Collation_Default +} + +func (st StatusIgnoredTable) Partitions(*sql.Context) (sql.PartitionIter, error) { + return index.SinglePartitionIterFromNomsMap(nil), nil +} + +func (st StatusIgnoredTable) PartitionRows(context *sql.Context, _ sql.Partition) (sql.RowIter, error) { + return newStatusIgnoredItr(context, &st) +} + +// NewStatusIgnoredTable creates a new StatusIgnoredTable using either an integrators' [adapters.TableAdapter] or the +// NewStatusIgnoredTableWithNoAdapter constructor (the default implementation provided by Dolt). +func NewStatusIgnoredTable(ctx *sql.Context, tableName string, ddb *doltdb.DoltDB, ws *doltdb.WorkingSet, rp env.RootsProvider[*sql.Context]) sql.Table { + adapter, ok := adapters.DoltTableAdapterRegistry.GetAdapter(tableName) + if ok { + return adapter.NewTable(ctx, tableName, ddb, ws, rp) + } + + return NewStatusIgnoredTableWithNoAdapter(ctx, tableName, ddb, ws, rp) +} + +// NewStatusIgnoredTableWithNoAdapter returns a new StatusIgnoredTable. +func NewStatusIgnoredTableWithNoAdapter(_ *sql.Context, tableName string, ddb *doltdb.DoltDB, ws *doltdb.WorkingSet, rp env.RootsProvider[*sql.Context]) sql.Table { + return &StatusIgnoredTable{ + tableName: tableName, + ddb: ddb, + workingSet: ws, + rootsProvider: rp, + } +} + +// StatusIgnoredItr is a sql.RowIter implementation for the status_ignored table. +type StatusIgnoredItr struct { + rows []statusIgnoredTableRow +} + +type statusIgnoredTableRow struct { + tableName string + status string + isStaged byte // not a bool bc wire protocol confuses bools and tinyint(1) + ignored bool +} + +func newStatusIgnoredItr(ctx *sql.Context, st *StatusIgnoredTable) (*StatusIgnoredItr, error) { + // If no roots provider was set, then there is no status to report + if st.rootsProvider == nil { + return &StatusIgnoredItr{rows: nil}, nil + } + + // Get the base status data using the shared function + statusRows, unstagedTables, err := getStatusRowsData(ctx, st.rootsProvider, st.workingSet) + if err != nil { + return nil, err + } + + // Get ignore patterns for checking if unstaged tables are ignored + ignorePatterns, err := getIgnorePatterns(ctx, st.rootsProvider) + if err != nil { + return nil, err + } + + // Build a set of unstaged table names for quick lookup + unstagedTableNames := buildUnstagedTableNameSet(unstagedTables) + + // Convert status rows to status_ignored rows, adding the ignored column + rows := make([]statusIgnoredTableRow, len(statusRows)) + for i, row := range statusRows { + ignored := false + // Only check ignore patterns for unstaged NEW tables (same as Git behavior). + // Tables that are modified, deleted, or renamed are already tracked, + // so ignore patterns don't apply to them. + if row.isStaged == byte(0) && row.status == newTableStatus && unstagedTableNames[row.tableName] { + tblNameObj := doltdb.TableName{Name: row.tableName} + result, err := ignorePatterns.IsTableNameIgnored(tblNameObj) + if err != nil { + return nil, err + } + if result == doltdb.Ignore { + ignored = true + } + } + rows[i] = statusIgnoredTableRow{ + tableName: row.tableName, + isStaged: row.isStaged, + status: row.status, + ignored: ignored, + } + } + + return &StatusIgnoredItr{rows: rows}, nil +} + +// getIgnorePatterns fetches the ignore patterns from the roots provider. +func getIgnorePatterns(ctx *sql.Context, rp env.RootsProvider[*sql.Context]) (doltdb.IgnorePatterns, error) { + roots, err := rp.GetRoots(ctx) + if err != nil { + return nil, err + } + schemas := []string{doltdb.DefaultSchemaName} + ignorePatternMap, err := doltdb.GetIgnoredTablePatterns(ctx, roots, schemas) + if err != nil { + return nil, err + } + return ignorePatternMap[doltdb.DefaultSchemaName], nil +} + +// buildUnstagedTableNameSet builds a set of unstaged table names for quick lookup. +func buildUnstagedTableNameSet(unstagedTables []diff.TableDelta) map[string]bool { + result := make(map[string]bool, len(unstagedTables)) + for _, td := range unstagedTables { + result[td.CurName()] = true + } + return result +} + +// Next retrieves the next row. It will return io.EOF if it's the last row. +func (itr *StatusIgnoredItr) Next(*sql.Context) (sql.Row, error) { + if len(itr.rows) <= 0 { + return nil, io.EOF + } + row := itr.rows[0] + itr.rows = itr.rows[1:] + return sql.NewRow(row.tableName, row.isStaged, row.status, row.ignored), nil +} + +// Close closes the iterator. +func (itr *StatusIgnoredItr) Close(*sql.Context) error { + return nil +} diff --git a/go/libraries/doltcore/sqle/dtables/status_table.go b/go/libraries/doltcore/sqle/dtables/status_table.go index 8213544981..de325486a3 100644 --- a/go/libraries/doltcore/sqle/dtables/status_table.go +++ b/go/libraries/doltcore/sqle/dtables/status_table.go @@ -115,6 +115,7 @@ type statusTableRow struct { // of this table when you are using local vs remote sql connections. } +// containsTableName checks if a table name is in the list of table names. func containsTableName(name string, names []doltdb.TableName) bool { for _, s := range names { if s.String() == name { @@ -124,21 +125,26 @@ func containsTableName(name string, names []doltdb.TableName) bool { return false } -func newStatusItr(ctx *sql.Context, st *StatusTable) (*StatusItr, error) { - // If no roots provider was set, then there is no status to report - rp := st.rootsProvider +// getStatusRowsData collects all status data from the given roots and working set. +// This is the shared logic used by both dolt_status and dolt_status_ignored tables. +// Returns the rows data along with the unstaged table deltas (needed for ignore checking). +func getStatusRowsData( + ctx *sql.Context, + rp env.RootsProvider[*sql.Context], + ws *doltdb.WorkingSet, +) ([]statusTableRow, []diff.TableDelta, error) { if rp == nil { - return &StatusItr{rows: nil}, nil + return nil, nil, nil } roots, err := rp.GetRoots(ctx) if err != nil { - return nil, err + return nil, nil, err } stagedTables, unstagedTables, err := diff.GetStagedUnstagedTableDeltas(ctx, roots) if err != nil { - return nil, err + return nil, nil, err } // Some tables may differ only in column tags and/or recorded conflicts. @@ -147,7 +153,7 @@ func newStatusItr(ctx *sql.Context, st *StatusTable) (*StatusItr, error) { for _, unstagedTableDiff := range unstagedTables { changed, err := unstagedTableDiff.HasChangesIgnoringColumnTags(ctx) if err != nil { - return nil, err + return nil, nil, err } if changed { changedUnstagedTables = append(changedUnstagedTables, unstagedTableDiff) @@ -157,30 +163,30 @@ func newStatusItr(ctx *sql.Context, st *StatusTable) (*StatusItr, error) { stagedSchemas, unstagedSchemas, err := diff.GetStagedUnstagedDatabaseSchemaDeltas(ctx, roots) if err != nil { - return nil, err + return nil, nil, err } rows := make([]statusTableRow, 0, len(stagedTables)+len(unstagedTables)+len(stagedSchemas)+len(unstagedSchemas)) cvTables, err := doltdb.TablesWithConstraintViolations(ctx, roots.Working) if err != nil { - return nil, err + return nil, nil, err } for _, tbl := range cvTables { rows = append(rows, statusTableRow{ tableName: tbl.String(), - status: "constraint violation", + status: constraintViolationStatus, }) } - if st.workingSet.MergeActive() { - ms := st.workingSet.MergeState() + if ws.MergeActive() { + ms := ws.MergeState() for _, tbl := range ms.TablesWithSchemaConflicts() { rows = append(rows, statusTableRow{ tableName: tbl.String(), isStaged: byte(0), - status: "schema conflict", + status: schemaConflictStatus, }) } @@ -195,7 +201,7 @@ func newStatusItr(ctx *sql.Context, st *StatusTable) (*StatusItr, error) { cnfTables, err := doltdb.TablesWithDataConflicts(ctx, roots.Working) if err != nil { - return nil, err + return nil, nil, err } for _, tbl := range cnfTables { rows = append(rows, statusTableRow{ @@ -218,6 +224,7 @@ func newStatusItr(ctx *sql.Context, st *StatusTable) (*StatusItr, error) { status: statusString(td), }) } + for _, td := range unstagedTables { tblName := tableName(td) if doltdb.IsFullTextTable(tblName) { @@ -249,6 +256,20 @@ func newStatusItr(ctx *sql.Context, st *StatusTable) (*StatusItr, error) { }) } + return rows, unstagedTables, nil +} + +func newStatusItr(ctx *sql.Context, st *StatusTable) (*StatusItr, error) { + // If no roots provider was set, then there is no status to report + if st.rootsProvider == nil { + return &StatusItr{rows: nil}, nil + } + + rows, _, err := getStatusRowsData(ctx, st.rootsProvider, st.workingSet) + if err != nil { + return nil, err + } + return &StatusItr{rows: rows}, nil } @@ -272,7 +293,7 @@ func tableName(td diff.TableDelta) string { func statusString(td diff.TableDelta) string { if td.IsAdd() { - return "new table" + return newTableStatus } else if td.IsDrop() { return "deleted" } else if td.IsRename() { @@ -284,6 +305,9 @@ func statusString(td diff.TableDelta) string { const mergeConflictStatus = "conflict" const mergedStatus = "merged" +const schemaConflictStatus = "schema conflict" +const constraintViolationStatus = "constraint violation" +const newTableStatus = "new table" // Next retrieves the next row. It will return io.EOF if it's the last row. // After retrieving the last row, Close will be automatically closed. diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go index 8fcd279daa..f0436f4a79 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go @@ -759,6 +759,157 @@ var DoltScripts = []queries.ScriptTest{ }, }, }, + { + Name: "dolt_status_ignored basic tests", + SetUpScript: []string{ + "CREATE TABLE t (pk int primary key);", + "INSERT INTO dolt_ignore VALUES ('ignored_*', true);", + "CREATE TABLE ignored_test (pk int primary key);", + }, + Assertions: []queries.ScriptTestAssertion{ + { + // Verify schema has 4 columns + Query: "DESCRIBE dolt_status_ignored;", + Expected: []sql.Row{ + {"table_name", "text", "NO", "PRI", nil, ""}, + {"staged", "tinyint(1)", "NO", "PRI", nil, ""}, + {"status", "text", "NO", "PRI", nil, ""}, + {"ignored", "tinyint(1)", "NO", "", nil, ""}, + }, + }, + { + // Non-ignored unstaged table has ignored=false + Query: "SELECT table_name, staged, status, ignored FROM dolt_status_ignored WHERE table_name = 't';", + Expected: []sql.Row{{"t", byte(0), "new table", false}}, + }, + { + // Ignored unstaged table has ignored=true + Query: "SELECT table_name, staged, status, ignored FROM dolt_status_ignored WHERE table_name = 'ignored_test';", + Expected: []sql.Row{{"ignored_test", byte(0), "new table", true}}, + }, + { + // dolt_ignore table itself shows as not ignored + Query: "SELECT table_name, ignored FROM dolt_status_ignored WHERE table_name = 'dolt_ignore';", + Expected: []sql.Row{{"dolt_ignore", false}}, + }, + }, + }, + { + Name: "dolt_status_ignored staged tables always have ignored=0", + SetUpScript: []string{ + // Create and stage table BEFORE adding ignore pattern + "CREATE TABLE staged_test (pk int primary key);", + "CALL DOLT_ADD('staged_test');", + // Now add pattern that matches the already-staged table name + "INSERT INTO dolt_ignore VALUES ('staged_*', true);", + }, + Assertions: []queries.ScriptTestAssertion{ + { + // Staged table has ignored=false even if name matches ignore pattern + Query: "SELECT table_name, staged, ignored FROM dolt_status_ignored WHERE table_name = 'staged_test';", + Expected: []sql.Row{{"staged_test", byte(1), false}}, + }, + }, + }, + { + Name: "dolt_status_ignored with AS OF and branch queries", + SetUpScript: []string{ + "CALL DOLT_COMMIT('--allow-empty', '-m', 'empty commit');", + "SET @commit1 = HASHOF('HEAD');", + "CALL DOLT_TAG('tag1');", + "CALL DOLT_CHECKOUT('-b', 'branch1');", + "CREATE TABLE abc (pk int);", + "CALL DOLT_ADD('abc');", + "CALL DOLT_CHECKOUT('main');", + "CREATE TABLE t (pk int primary key);", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT table_name, staged, status, ignored FROM dolt_status_ignored;", + Expected: []sql.Row{{"t", byte(0), "new table", false}}, + }, + { + Query: "SELECT * FROM dolt_status_ignored AS OF 'tag1';", + Expected: []sql.Row{}, + }, + { + Query: "SELECT * FROM dolt_status_ignored AS OF @commit1;", + Expected: []sql.Row{}, + }, + { + Query: "SELECT table_name, staged, status, ignored FROM dolt_status_ignored AS OF 'branch1';", + Expected: []sql.Row{{"abc", byte(1), "new table", false}}, + }, + { + Query: "SELECT table_name, staged, status, ignored FROM `mydb/branch1`.dolt_status_ignored;", + Expected: []sql.Row{{"abc", byte(1), "new table", false}}, + }, + }, + }, + { + Name: "dolt_status_ignored with multiple ignore patterns", + SetUpScript: []string{ + "INSERT INTO dolt_ignore VALUES ('temp_*', true);", + "INSERT INTO dolt_ignore VALUES ('*_backup', true);", + "CREATE TABLE temp_data (pk int primary key);", + "CREATE TABLE users_backup (pk int primary key);", + "CREATE TABLE normal_table (pk int primary key);", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT table_name, ignored FROM dolt_status_ignored WHERE table_name = 'temp_data';", + Expected: []sql.Row{{"temp_data", true}}, + }, + { + Query: "SELECT table_name, ignored FROM dolt_status_ignored WHERE table_name = 'users_backup';", + Expected: []sql.Row{{"users_backup", true}}, + }, + { + Query: "SELECT table_name, ignored FROM dolt_status_ignored WHERE table_name = 'normal_table';", + Expected: []sql.Row{{"normal_table", false}}, + }, + }, + }, + { + Name: "dolt_status_ignored with empty dolt_ignore", + SetUpScript: []string{ + "CREATE TABLE t1 (pk int primary key);", + "CREATE TABLE t2 (pk int primary key);", + }, + Assertions: []queries.ScriptTestAssertion{ + { + // With no ignore patterns, all tables should have ignored=false + Query: "SELECT table_name, ignored FROM dolt_status_ignored ORDER BY table_name;", + Expected: []sql.Row{ + {"t1", false}, + {"t2", false}, + }, + }, + }, + }, + { + Name: "dolt_status_ignored with conflicting patterns", + SetUpScript: []string{ + // First pattern ignores tables starting with "test_" + "INSERT INTO dolt_ignore VALUES ('test_*', true);", + // Second pattern un-ignores specific table + "INSERT INTO dolt_ignore VALUES ('test_special', false);", + "CREATE TABLE test_normal (pk int primary key);", + "CREATE TABLE test_special (pk int primary key);", + }, + Assertions: []queries.ScriptTestAssertion{ + { + // test_normal matches ignore pattern + Query: "SELECT table_name, ignored FROM dolt_status_ignored WHERE table_name = 'test_normal';", + Expected: []sql.Row{{"test_normal", true}}, + }, + { + // test_special is explicitly not ignored (false overrides wildcard) + Query: "SELECT table_name, ignored FROM dolt_status_ignored WHERE table_name = 'test_special';", + Expected: []sql.Row{{"test_special", false}}, + }, + }, + }, { Name: "dolt_hashof_table tests", SetUpScript: []string{ @@ -8670,6 +8821,7 @@ var DoltSystemVariables = []queries.ScriptTest{ {"dolt_remotes"}, {"dolt_stashes"}, {"dolt_status"}, + {"dolt_status_ignored"}, {"dolt_workspace_test"}, {"test"}, }, diff --git a/integration-tests/bats/ls.bats b/integration-tests/bats/ls.bats index fbea364d1a..6e1abe3abd 100755 --- a/integration-tests/bats/ls.bats +++ b/integration-tests/bats/ls.bats @@ -60,9 +60,10 @@ teardown() { @test "ls: --system shows system tables" { run dolt ls --system [ "$status" -eq 0 ] - [ "${#lines[@]}" -eq 26 ] + [ "${#lines[@]}" -eq 27 ] [[ "$output" =~ "System tables:" ]] || false [[ "$output" =~ "dolt_status" ]] || false + [[ "$output" =~ "dolt_status_ignored" ]] || false [[ "$output" =~ "dolt_commits" ]] || false [[ "$output" =~ "dolt_commit_ancestors" ]] || false [[ "$output" =~ "dolt_constraint_violations" ]] || false diff --git a/integration-tests/bats/system-tables.bats b/integration-tests/bats/system-tables.bats index f6546fbb93..64849b24e3 100644 --- a/integration-tests/bats/system-tables.bats +++ b/integration-tests/bats/system-tables.bats @@ -1165,3 +1165,70 @@ SQL [ "$status" -eq 0 ] [[ "$output" =~ "dolt_conflict_id" ]] || false } + +@test "system-tables: query dolt_status_ignored system table" { + # Create a table and add it + dolt sql -q "CREATE TABLE test (pk INT PRIMARY KEY, c1 INT)" + dolt add test + dolt commit -m "Added test table" + + # Create an ignored table pattern + dolt sql -q "INSERT INTO dolt_ignore VALUES ('ignored_*', true)" + + # Create a table that matches the ignore pattern + dolt sql -q "CREATE TABLE ignored_table (pk INT PRIMARY KEY)" + + # Create a non-ignored table with changes + dolt sql -q "INSERT INTO test VALUES (1, 1)" + + # Query dolt_status - shows all tables (does not filter ignored) + run dolt sql -q "SELECT table_name FROM dolt_status WHERE staged = false" + [ "$status" -eq 0 ] + [[ "$output" =~ "test" ]] || false + [[ "$output" =~ "ignored_table" ]] || false + + # Query dolt_status_ignored - shows all tables with ignored column + run dolt sql -q "SELECT table_name, ignored FROM dolt_status_ignored WHERE staged = false ORDER BY table_name" + [ "$status" -eq 0 ] + [[ "$output" =~ "ignored_table" ]] || false + [[ "$output" =~ "test" ]] || false + + # Verify ignored column correctly identifies ignored tables + run dolt sql -r csv -q "SELECT table_name, ignored FROM dolt_status_ignored WHERE table_name = 'ignored_table'" + [ "$status" -eq 0 ] + [[ "$output" =~ "ignored_table,true" ]] || false + + # Verify non-ignored table has ignored = false + run dolt sql -r csv -q "SELECT table_name, ignored FROM dolt_status_ignored WHERE table_name = 'test'" + [ "$status" -eq 0 ] + [[ "$output" =~ "test,false" ]] || false + + # Verify schema has 4 columns + run dolt sql -q "DESCRIBE dolt_status_ignored" + [ "$status" -eq 0 ] + [[ "$output" =~ "table_name" ]] || false + [[ "$output" =~ "staged" ]] || false + [[ "$output" =~ "status" ]] || false + [[ "$output" =~ "ignored" ]] || false +} + +@test "system-tables: dolt_status_ignored shows staged tables without ignored flag" { + # Create and stage a table + dolt sql -q "CREATE TABLE staged_test (pk INT PRIMARY KEY)" + dolt add staged_test + + # Query staged tables - should have ignored = false + run dolt sql -q "SELECT table_name, ignored FROM dolt_status_ignored WHERE staged = true AND table_name = 'staged_test'" + [ "$status" -eq 0 ] + [[ "$output" =~ "staged_test" ]] || false + # Staged tables should never be marked as ignored + run dolt sql -r csv -q "SELECT ignored FROM dolt_status_ignored WHERE staged = true AND table_name = 'staged_test'" + [ "$status" -eq 0 ] + [[ "$output" =~ "false" ]] || false +} + +@test "system-tables: dolt_status_ignored shows in dolt ls --system" { + run dolt ls --system + [ "$status" -eq 0 ] + [[ "$output" =~ "dolt_status_ignored" ]] || false +}