Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
92cb7dc
add implementation for new `@` database revision delimiter for use in…
elianddb Feb 5, 2026
6ed4751
add new `ScriptTest`s in `DoltRevisionDbScripts` for database revisio…
elianddb Feb 5, 2026
f303fd4
fix ORM CI tests to run automatically, add new Prisma branch test usi…
elianddb Feb 5, 2026
48f708d
rm unused orm-tests github action
elianddb Feb 6, 2026
1040b9a
fix behavior when `dolt_show_branch_databases` interacts with `dolt_e…
elianddb Feb 6, 2026
26c2de9
fix sql shell current database to resolve new delimiter and fix revis…
elianddb Feb 7, 2026
c1de39e
mv docker orm tests to separate branch
elianddb Feb 12, 2026
e196152
fix shell promp current db split system
elianddb Feb 13, 2026
792ce9d
add tests for commit revisions
elianddb Feb 13, 2026
bb71e47
amend sess var querying
elianddb Feb 13, 2026
fa4da6b
rm configuration for delimiter
elianddb Feb 16, 2026
6a0b0fc
fix normal db compatibility that use `@` character
elianddb Feb 19, 2026
e078b89
implement full prompt resolver for base db, revision, and dirty state
elianddb Feb 19, 2026
3a532d4
amend base db and rev resolver to use new active_revision() and base_…
elianddb Feb 20, 2026
332316e
add skip on windows
elianddb Feb 20, 2026
37b922f
fix dolt_status check on non-dolt databases
elianddb Feb 20, 2026
4a77378
mv delimiter alias to be parsed on last index
elianddb Feb 21, 2026
5841bf0
amend sql-shell resolver to evaluate using `active_branch()` and `dat…
elianddb Feb 25, 2026
a436dac
Merge remote-tracking branch 'origin/main' into elian/10382
elianddb Feb 25, 2026
fd4c81f
amend to gms pr
elianddb Feb 25, 2026
3b583ef
fix dolt_status test
elianddb Feb 25, 2026
74d5d60
mv delimiter checks to dolt
elianddb Feb 25, 2026
451b3af
Merge remote-tracking branch 'origin/main' into elian/10382
elianddb Feb 25, 2026
831952b
amend gms ver.
elianddb Feb 25, 2026
cb6dc3b
amend delimiter precedence to be the same left-to-right evaluation, a…
elianddb Feb 26, 2026
f49fdf6
Merge remote-tracking branch 'origin/main' into elian/10382
elianddb Feb 26, 2026
ca74006
amend gms ver. to dolthub/go-mysql-server#3448
elianddb Feb 26, 2026
b6eaad2
rm string.ToLower() on SessionDatabase entirely, gms expects case-sen…
elianddb Feb 26, 2026
710564a
Merge remote-tracking branch 'origin/main' into elian/10382
elianddb Feb 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ CLAUDE.md
.gitattributes

.de/
.cursor
AGENTS.md
.claude/
.cursorrules
145 changes: 145 additions & 0 deletions go/cmd/dolt/cli/prompt/resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// 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 prompt

import (
"errors"
"strings"

"github.com/dolthub/go-mysql-server/sql"

"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
)

// Parts contains shell prompt components to render the SQL shell prompt.
type Parts struct {
BaseDatabase string
ActiveRevision string
RevisionDelimiter string
IsBranch bool
Dirty bool
}

// Resolver resolves prompt [prompt.Parts] for the active session.
type Resolver interface {
Resolve(sqlCtx *sql.Context, queryist cli.Queryist) (parts Parts, resolved bool, err error)
}

// sqlDBActiveBranchResolver can resolve [prompt.Parts] using the Dolt SQL functions, and supports
// [doltdb.DbRevisionDelimiter] and [doltdb.DbRevisionDelimiterAlias].
type sqlDBActiveBranchResolver struct{}

// chainedResolver can resolve [prompt.Parts] through the sequential execution [prompt.Resolver](s).
type chainedResolver struct {
resolvers []Resolver
}

// NewPromptResolver constructs an up-to-date [prompt.Resolver].
func NewPromptResolver() Resolver {
return chainedResolver{
resolvers: []Resolver{
sqlDBActiveBranchResolver{},
},
}
}

// Resolve resolves [prompt.Parts] through a chain of [prompt.Resolver](s) in sequential order. If a Resolver encounters
// an error, it is returned immediately and no other Resolver executes.
func (cr chainedResolver) Resolve(sqlCtx *sql.Context, queryist cli.Queryist) (parts Parts, resolved bool, err error) {
for _, resolver := range cr.resolvers {
parts, resolved, err := resolver.Resolve(sqlCtx, queryist)
if err != nil {
return Parts{}, false, err
}
if resolved {
return parts, true, nil
}
}
return Parts{}, false, nil
}

// Resolve resolves the base DB and revision through the SQL function `database()` and Dolt-specific `active_branch()`.
func (sqlDBActiveBranchResolver) Resolve(sqlCtx *sql.Context, queryist cli.Queryist) (parts Parts, resolved bool, err error) {
dbRows, err := cli.GetRowsForSql(queryist, sqlCtx, "select database() as db")
if err != nil {
return parts, false, err
}
if len(dbRows) > 0 && len(dbRows[0]) > 0 {
dbName, err := cli.QueryValueAsString(dbRows[0][0])
if err != nil {
return parts, false, err
}
// Handles non-branch revisions (i.e., commit hash, tags, etc.).
parts.BaseDatabase, parts.ActiveRevision = doltdb.SplitRevisionDbName(dbName)

parts.RevisionDelimiter = doltdb.DbRevisionDelimiter
for _, delimiter := range doltdb.DBRevisionDelimiters {
if strings.Contains(dbName, delimiter) {
parts.RevisionDelimiter = delimiter
break
}
}
}

activeBranchRows, err := cli.GetRowsForSql(queryist, sqlCtx, "select active_branch() as branch")
if err != nil {
return parts, false, err
}

if len(activeBranchRows) > 0 && len(activeBranchRows[0]) > 0 {
parts.IsBranch = activeBranchRows[0][0] != nil
if parts.ActiveRevision == "" {
parts.ActiveRevision, err = cli.QueryValueAsString(activeBranchRows[0][0])
if err != nil {
return parts, false, err
}
}
}

parts.Dirty, err = resolveDirty(sqlCtx, queryist, parts)
if err != nil {
return parts, false, err
}

return parts, true, nil
}

// resolveDirty resolves the dirty state of the current branch and whether the revision type is a branch.
func resolveDirty(sqlCtx *sql.Context, queryist cli.Queryist, parts Parts) (dirty bool, err error) {
if doltdb.IsValidCommitHash(parts.ActiveRevision) {
return false, nil
}

rows, err := cli.GetRowsForSql(queryist, sqlCtx, "select count(table_name) > 0 as dirty from dolt_status")
// [sql.ErrTableNotFound] detects when viewing a non-Dolt database (e.g., information_schema). Older servers may
// complain about [doltdb.ErrOperationNotSupportedInDetachedHead], but read-only revisions in newer versions this
// issue should be gone.
if errors.Is(err, doltdb.ErrOperationNotSupportedInDetachedHead) || sql.ErrTableNotFound.Is(err) {
return false, nil
} else if err != nil {
return false, err
}

if len(rows) == 0 || len(rows[0]) == 0 {
return false, nil
}
dirty, err = cli.QueryValueAsBool(rows[0][0])
if err != nil {
return false, err
}

return dirty, nil
}
89 changes: 68 additions & 21 deletions go/cmd/dolt/cli/query_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,11 @@ package cli

import (
"fmt"
"strings"

"github.com/dolthub/go-mysql-server/sql"
)

// GetInt8ColAsBool returns the value of an int8 column as a bool
// This is necessary because Queryist may return an int8 column as a bool (when using SQLEngine)
// or as a string (when using ConnectionQueryist).
func GetInt8ColAsBool(col interface{}) (bool, error) {
switch v := col.(type) {
case int8:
return v != 0, nil
case string:
if v == "ON" || v == "1" {
return true, nil
} else if v == "OFF" || v == "0" {
return false, nil
} else {
return false, fmt.Errorf("unexpected value for boolean var: %v", v)
}
default:
return false, fmt.Errorf("unexpected type %T, was expecting int8", v)
}
}

// SetSystemVar sets the @@dolt_show_system_tables variable if necessary, and returns a function
// resetting the variable for after the commands completion, if necessary.
func SetSystemVar(queryist Queryist, sqlCtx *sql.Context, newVal bool) (func() error, error) {
Expand All @@ -52,7 +33,7 @@ func SetSystemVar(queryist Queryist, sqlCtx *sql.Context, newVal bool) (func() e
if err != nil {
return nil, err
}
prevVal, err := GetInt8ColAsBool(row[0][1])
prevVal, err := QueryValueAsBool(row[0][1])
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -83,3 +64,69 @@ func GetRowsForSql(queryist Queryist, sqlCtx *sql.Context, query string) ([]sql.

return rows, nil
}

// QueryValueAsString converts a single value from a query result to a string. Use this when reading string-like
// columns from Queryist results, since the type can differ in-process [engine.SQLEngine] versus over the wire
// [sqlserver.ConnectionQueryist].
func QueryValueAsString(value any) (str string, err error) {
if value == nil {
return "", nil
}

switch v := value.(type) {
case string:
return v, nil
case []byte:
return string(v), nil
case fmt.Stringer:
return v.String(), nil
default:
return "", fmt.Errorf("unexpected type %T, expected string-like column value", value)
}
}

// QueryValueAsBool interprets a query result cell as a bool. Strings are normalized and matched as "true"/"1"/"ON"
// (true) or the opposite for false; matching is case-insensitive. [Queryist] may return a tinyint column as a bool
// when utilizing the [engine.SQLEngine] or as string when using [sqlserver.ConnectionQueryist].
func QueryValueAsBool(col interface{}) (bool, error) {
switch v := col.(type) {
case bool:
return v, nil
case byte:
return v == 1, nil
case int:
return v == 1, nil
case int8:
return v == 1, nil
case string:
s := strings.TrimSpace(v)
switch {
case s == "1" || strings.EqualFold(s, "true") || strings.EqualFold(s, "ON"):
return true, nil
case s == "0" || strings.EqualFold(s, "false") || strings.EqualFold(s, "OFF"):
return false, nil
default:
return false, fmt.Errorf("unexpected value for string for bool: %v", v)
}
default:
return false, fmt.Errorf("unexpected type %T, was expecting bool, int, or string", v)
}
}

// WithQueryWarningsLocked runs queries with a preserved warning buffer. Internal shell queries run on the same SQL
// session as user queries. Without warning locks, these housekeeping queries can clear or overwrite warnings that users
// expect.
func WithQueryWarningsLocked(sqlCtx *sql.Context, queryist Queryist, fn func() error) error {
_, _, _, err := queryist.Query(sqlCtx, "set lock_warnings = 1")
if err != nil {
return err
}

runErr := fn()

_, _, _, err = queryist.Query(sqlCtx, "set lock_warnings = 0")
if err != nil {
return err
}
return runErr
}
2 changes: 1 addition & 1 deletion go/cmd/dolt/commands/cnfcmds/cat.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ func getMergeStatus(queryist cli.Queryist, sqlCtx *sql.Context) (mergeStatus, er
}

row := rows[0]
ms.isMerging, err = commands.GetTinyIntColAsBool(row[0])
ms.isMerging, err = cli.QueryValueAsBool(row[0])
if err != nil {
return ms, fmt.Errorf("error: failed to parse is_merging: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions go/cmd/dolt/commands/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -859,11 +859,11 @@ func getDiffSummariesBetweenRefs(queryist cli.Queryist, sqlCtx *sql.Context, fro
summary.FromTableName.Name = row[0].(string)
summary.ToTableName.Name = row[1].(string)
summary.DiffType = row[2].(string)
summary.DataChange, err = GetTinyIntColAsBool(row[3])
summary.DataChange, err = cli.QueryValueAsBool(row[3])
if err != nil {
return nil, fmt.Errorf("error: unable to parse data change value '%s': %w", row[3], err)
}
summary.SchemaChange, err = GetTinyIntColAsBool(row[4])
summary.SchemaChange, err = cli.QueryValueAsBool(row[4])
if err != nil {
return nil, fmt.Errorf("error: unable to parse schema change value '%s': %w", row[4], err)
}
Expand Down
Loading
Loading