Skip to content

Commit

Permalink
Add \pager support to dolt shell
Browse files Browse the repository at this point in the history
  • Loading branch information
macneale4 committed Feb 28, 2025
1 parent 1b0f660 commit 82f843b
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 46 deletions.
2 changes: 1 addition & 1 deletion go/cmd/dolt/commands/blame.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func (cmd BlameCmd) Exec(ctx context.Context, commandStr string, args []string,
return 1
}

err = engine.PrettyPrintResults(sqlCtx, engine.FormatTabular, schema, ri)
err = engine.PrettyPrintResults(sqlCtx, engine.FormatTabular, schema, ri, false)
if err != nil {
iohelp.WriteLine(cli.CliOut, err.Error())
return 1
Expand Down
2 changes: 1 addition & 1 deletion go/cmd/dolt/commands/cvcmds/verify_constraints.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func printViolationsForTable(ctx context.Context, dbName, tblName string, tbl *d

limitItr := &sqlLimitIter{itr: sqlItr, limit: 50}

err = engine.PrettyPrintResults(sCtx, engine.FormatTabular, sqlSch, limitItr)
err = engine.PrettyPrintResults(sCtx, engine.FormatTabular, sqlSch, limitItr, false)
if err != nil {
return errhand.BuildDError("Error outputting rows").AddCause(err).Build()
}
Expand Down
79 changes: 47 additions & 32 deletions go/cmd/dolt/commands/engine/sql_print.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"io"
"time"

"github.com/dolthub/dolt/go/store/util/outputpager"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/types"

Expand Down Expand Up @@ -54,16 +55,16 @@ const (
)

// PrettyPrintResults prints the result of a query in the format provided
func PrettyPrintResults(ctx *sql.Context, resultFormat PrintResultFormat, sqlSch sql.Schema, rowIter sql.RowIter) (rerr error) {
return prettyPrintResultsWithSummary(ctx, resultFormat, sqlSch, rowIter, PrintNoSummary)
func PrettyPrintResults(ctx *sql.Context, resultFormat PrintResultFormat, sqlSch sql.Schema, rowIter sql.RowIter, pageResults bool) (rerr error) {
return prettyPrintResultsWithSummary(ctx, resultFormat, sqlSch, rowIter, PrintNoSummary, pageResults)
}

// PrettyPrintResultsExtended prints the result of a query in the format provided, including row count and timing info
func PrettyPrintResultsExtended(ctx *sql.Context, resultFormat PrintResultFormat, sqlSch sql.Schema, rowIter sql.RowIter) (rerr error) {
return prettyPrintResultsWithSummary(ctx, resultFormat, sqlSch, rowIter, PrintRowCountAndTiming)
func PrettyPrintResultsExtended(ctx *sql.Context, resultFormat PrintResultFormat, sqlSch sql.Schema, rowIter sql.RowIter, pageResults bool) (rerr error) {
return prettyPrintResultsWithSummary(ctx, resultFormat, sqlSch, rowIter, PrintRowCountAndTiming, pageResults)
}

func prettyPrintResultsWithSummary(ctx *sql.Context, resultFormat PrintResultFormat, sqlSch sql.Schema, rowIter sql.RowIter, summary PrintSummaryBehavior) (rerr error) {
func prettyPrintResultsWithSummary(ctx *sql.Context, resultFormat PrintResultFormat, sqlSch sql.Schema, rowIter sql.RowIter, summary PrintSummaryBehavior, pageResults bool) (rerr error) {
defer func() {
closeErr := rowIter.Close(ctx)
if rerr == nil && closeErr != nil {
Expand All @@ -79,37 +80,51 @@ func prettyPrintResultsWithSummary(ctx *sql.Context, resultFormat PrintResultFor
}

var wr table.SqlRowWriter

switch resultFormat {
case FormatCsv:
var err error
wr, err = csv.NewCSVSqlWriter(iohelp.NopWrCloser(cli.CliOut), sqlSch, csv.NewCSVInfo())
if err != nil {
return err
}
case FormatJson:
var err error
wr, err = json.NewJSONSqlWriter(iohelp.NopWrCloser(cli.CliOut), sqlSch)
if err != nil {
return err
var err error
var numRows int

printEm := func() {
writerStream := cli.CliOut
if pageResults {
pager := outputpager.Start()
defer pager.Stop()
writerStream = pager.Writer
}
case FormatTabular:
wr = tabular.NewFixedWidthTableWriter(sqlSch, iohelp.NopWrCloser(cli.CliOut), 100)
case FormatNull:
wr = nullWriter{}
case FormatVertical:
wr = newVerticalRowWriter(iohelp.NopWrCloser(cli.CliOut), sqlSch)
case FormatParquet:
var err error
wr, err = parquet.NewParquetRowWriter(sqlSch, iohelp.NopWrCloser(cli.CliOut))
if err != nil {
return err

switch resultFormat {
case FormatCsv:
var err error
wr, err = csv.NewCSVSqlWriter(iohelp.NopWrCloser(writerStream), sqlSch, csv.NewCSVInfo())
if err != nil {
return
}
case FormatJson:
var err error
wr, err = json.NewJSONSqlWriter(iohelp.NopWrCloser(writerStream), sqlSch)
if err != nil {
return
}
case FormatTabular:
wr = tabular.NewFixedWidthTableWriter(sqlSch, iohelp.NopWrCloser(writerStream), 100)
case FormatNull:
wr = nullWriter{}
case FormatVertical:
wr = newVerticalRowWriter(iohelp.NopWrCloser(writerStream), sqlSch)
case FormatParquet:
var err error
wr, err = parquet.NewParquetRowWriter(sqlSch, iohelp.NopWrCloser(writerStream))
if err != nil {
return
}
}

numRows, err = writeResultSet(ctx, rowIter, wr)
}

numRows, err := writeResultSet(ctx, rowIter, wr)
if err != nil {
return err
if pageResults {
cli.ExecuteWithStdioRestored(printEm)
} else {
printEm()
}

// if there is no row data and result format is JSON, then create empty JSON.
Expand Down
2 changes: 1 addition & 1 deletion go/cmd/dolt/commands/schcmds/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func (cmd TagsCmd) Exec(ctx context.Context, commandStr string, args []string, d
}

sqlCtx := sql.NewContext(ctx)
err = engine.PrettyPrintResults(sqlCtx, outputFmt, headerSchema, sql.RowsToRowIter(rows...))
err = engine.PrettyPrintResults(sqlCtx, outputFmt, headerSchema, sql.RowsToRowIter(rows...), false)

return commands.HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
}
34 changes: 23 additions & 11 deletions go/cmd/dolt/commands/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ func listSavedQueries(ctx *sql.Context, qryist cli.Queryist, dEnv *env.DoltEnv,
}

query := "SELECT * FROM " + doltdb.DoltQueryCatalogTableName
return sqlHandleVErrAndExitCode(qryist, execQuery(ctx, qryist, query, format), usage)
return sqlHandleVErrAndExitCode(qryist, execSingleQuery(ctx, qryist, query, format), usage)
}

func executeSavedQuery(ctx *sql.Context, qryist cli.Queryist, dEnv *env.DoltEnv, savedQueryName string, format engine.PrintResultFormat, usage cli.UsagePrinter) int {
Expand All @@ -376,7 +376,7 @@ func executeSavedQuery(ctx *sql.Context, qryist cli.Queryist, dEnv *env.DoltEnv,
}

cli.PrintErrf("Executing saved query '%s':\n%s\n", savedQueryName, sq.Query)
return sqlHandleVErrAndExitCode(qryist, execQuery(ctx, qryist, sq.Query, format), usage)
return sqlHandleVErrAndExitCode(qryist, execSingleQuery(ctx, qryist, sq.Query, format), usage)
}

func queryMode(
Expand Down Expand Up @@ -406,7 +406,7 @@ func execSaveQuery(ctx *sql.Context, dEnv *env.DoltEnv, qryist cli.Queryist, apr

saveName := apr.GetValueOrDefault(saveFlag, "")

verr := execQuery(ctx, qryist, query, format)
verr := execSingleQuery(ctx, qryist, query, format)
if verr != nil {
return sqlHandleVErrAndExitCode(qryist, verr, usage)
}
Expand All @@ -430,7 +430,9 @@ func execSaveQuery(ctx *sql.Context, dEnv *env.DoltEnv, qryist cli.Queryist, apr
return 0
}

func execQuery(
// execSingleQuery runs a single query and prints the results. This is not intended for use in interactive modes, especially
// the shell.
func execSingleQuery(
sqlCtx *sql.Context,
qryist cli.Queryist,
query string,
Expand All @@ -443,7 +445,7 @@ func execQuery(
}

if rowIter != nil {
err = engine.PrettyPrintResults(sqlCtx, format, sqlSch, rowIter)
err = engine.PrettyPrintResults(sqlCtx, format, sqlSch, rowIter, false)
if err != nil {
return errhand.VerboseErrorFromError(err)
}
Expand Down Expand Up @@ -659,7 +661,7 @@ func execBatchMode(ctx *sql.Context, qryist cli.Queryist, input io.Reader, conti
fileReadProg.printNewLineIfNeeded()
}
}
err = engine.PrettyPrintResults(ctx, format, sqlSch, rowIter)
err = engine.PrettyPrintResults(ctx, format, sqlSch, rowIter, false)
if err != nil {
err = buildBatchSqlErr(scanner.state.statementStartLine, query, err)
if !continueOnErr {
Expand Down Expand Up @@ -749,6 +751,7 @@ func execShell(sqlCtx *sql.Context, qryist cli.Queryist, format engine.PrintResu

initialCtx := sqlCtx.Context

pagerEnabled := false
// Used for the \edit command.
lastSqlCmd := ""

Expand Down Expand Up @@ -787,9 +790,18 @@ func execShell(sqlCtx *sql.Context, qryist cli.Queryist, format engine.PrintResu
}

if cmdType == DoltCliCommand {
err := handleSlashCommand(sqlCtx, subCmd, query, cliCtx)
if err != nil {
shell.Println(color.RedString(err.Error()))
if _, ok := subCmd.(SlashPager); ok {
p, err := handlePagerCommand(query)
if err != nil {
shell.Println(color.RedString(err.Error()))
} else {
pagerEnabled = p
}
} else {
err := handleSlashCommand(sqlCtx, subCmd, query, cliCtx)
if err != nil {
shell.Println(color.RedString(err.Error()))
}
}
} else {
if cmdType == TransformCommand {
Expand All @@ -805,9 +817,9 @@ func execShell(sqlCtx *sql.Context, qryist cli.Queryist, format engine.PrintResu
} else if rowIter != nil {
switch closureFormat {
case engine.FormatTabular, engine.FormatVertical:
err = engine.PrettyPrintResultsExtended(sqlCtx, closureFormat, sqlSch, rowIter)
err = engine.PrettyPrintResultsExtended(sqlCtx, closureFormat, sqlSch, rowIter, pagerEnabled)
default:
err = engine.PrettyPrintResults(sqlCtx, closureFormat, sqlSch, rowIter)
err = engine.PrettyPrintResults(sqlCtx, closureFormat, sqlSch, rowIter, pagerEnabled)
}

if err != nil {
Expand Down
51 changes: 51 additions & 0 deletions go/cmd/dolt/commands/sql_slash.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ var slashCmds = []cli.Command{
MergeCmd{},
SlashHelp{},
SlashEdit{},
SlashPager{},
}

// parseSlashCmd parses a command line string into a slice of strings, splitting on spaces, but allowing spaces within
Expand Down Expand Up @@ -215,3 +216,53 @@ func (s SlashEdit) ArgParser() *argparser.ArgParser {
// No arguments.
return &argparser.ArgParser{}
}

type SlashPager struct{}

func (s SlashPager) Docs() *cli.CommandDocumentation {
//TODO
return &cli.CommandDocumentation{}
}

func (s SlashPager) ArgParser() *argparser.ArgParser {
return &argparser.ArgParser{}
}

var _ cli.Command = SlashPager{}

func (s SlashPager) Name() string {
return "pager"
}
func (s SlashPager) Description() string {
return "Enable or Disable the result pager"
}

// Exec is a little special because the shell is interested in the response. So rather than call Exec, it calls
// handlePagerCommand function.
func (s SlashPager) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx cli.CliContext) int {
panic("runtime error. SlashPager.Exec should never be called.")
}

// handlePagerCommand executes the pager command and returns true if pager should be on, or false otherwise. An error
// could come up if they provided weird input.
func handlePagerCommand(fullCmd string) (bool, error) {
tokens := strings.Split(fullCmd, " ")

if len(tokens) == 0 || tokens[0] != "\\pager" {
return false, fmt.Errorf("runtime error: Expected \\pager command.")
}

if len(tokens) == 1 {
return false, fmt.Errorf("Usage: \\pager [on|off]")
}

// Kind of sloppy here,`\pager foo bar on` will work, but not the end of the world.
if tokens[len(tokens)-1] == "on" {
return true, nil
}
if tokens[len(tokens)-1] == "off" {
return false, nil
}

return false, fmt.Errorf("Usage: \\pager [on|off]")
}

0 comments on commit 82f843b

Please sign in to comment.