Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add pager support to sql shell #8905

Merged
merged 3 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
81 changes: 51 additions & 30 deletions go/cmd/dolt/commands/engine/sql_print.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/dolthub/dolt/go/libraries/doltcore/table/untyped/csv"
"github.com/dolthub/dolt/go/libraries/doltcore/table/untyped/tabular"
"github.com/dolthub/dolt/go/libraries/utils/iohelp"
"github.com/dolthub/dolt/go/store/util/outputpager"
)

type PrintResultFormat byte
Expand All @@ -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,35 +80,55 @@ 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
var err error
var numRows int

// Function to print results. A function is required because we need to wrap the whole process in a swap of
// IO streams. This is done with cli.ExecuteWithStdioRestored, which requires a resultless function. As
// a result, we need to depend on side effects to numRows and err to determine if it was successful.
printEm := func() {
writerStream := cli.CliOut
if pageResults {
pager := outputpager.Start()
defer pager.Stop()
writerStream = pager.Writer
}
case FormatJson:
var err error
wr, err = json.NewJSONSqlWriter(iohelp.NopWrCloser(cli.CliOut), sqlSch)
if err != nil {
return err
}
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 pageResults {
cli.ExecuteWithStdioRestored(printEm)
} else {
printEm()
}
if err != nil {
return err
}
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]")
}
Loading