From a3e6bbaf88e2e95f2d2c6f65884101701f00f71a Mon Sep 17 00:00:00 2001 From: elianddb Date: Fri, 25 Jul 2025 20:06:31 +0000 Subject: [PATCH] mv impl to print layer --- go/cmd/dolt/commands/blame.go | 2 +- .../commands/cvcmds/verify_constraints.go | 2 +- go/cmd/dolt/commands/engine/sql_print.go | 65 ++++++++++++- go/cmd/dolt/commands/schcmds/tags.go | 2 +- go/cmd/dolt/commands/sql.go | 97 ++++--------------- .../dolt/commands/sqlserver/queryist_utils.go | 17 +--- go/libraries/doltcore/sqle/sqlutil/sql_row.go | 22 +++++ .../untyped/tabular/fixedwidth_tablewriter.go | 1 - ...-as-hex-flag-precedence-interactive.expect | 46 +++++++++ ...y-as-hex-server-default-interactive.expect | 41 ++++++++ .../binary-as-hex-server-interactive.expect | 23 ++++- ...binary-as-hex-skip-flag-interactive.expect | 30 +++++- integration-tests/bats/sql-server.bats | 55 +++++++++-- 13 files changed, 287 insertions(+), 116 deletions(-) create mode 100644 integration-tests/bats/binary-as-hex-flag-precedence-interactive.expect create mode 100644 integration-tests/bats/binary-as-hex-server-default-interactive.expect diff --git a/go/cmd/dolt/commands/blame.go b/go/cmd/dolt/commands/blame.go index 0ec7b9e5b9..c22045b0ed 100644 --- a/go/cmd/dolt/commands/blame.go +++ b/go/cmd/dolt/commands/blame.go @@ -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, false, false, true) + err = engine.PrettyPrintResults(sqlCtx, engine.FormatTabular, schema, ri, false, false, true, false) if err != nil { iohelp.WriteLine(cli.CliOut, err.Error()) return 1 diff --git a/go/cmd/dolt/commands/cvcmds/verify_constraints.go b/go/cmd/dolt/commands/cvcmds/verify_constraints.go index 3445cb6910..e473978781 100644 --- a/go/cmd/dolt/commands/cvcmds/verify_constraints.go +++ b/go/cmd/dolt/commands/cvcmds/verify_constraints.go @@ -181,7 +181,7 @@ func printViolationsForTable(ctx *sql.Context, dbName, tblName string, tbl *dolt limitItr := &sqlLimitIter{itr: sqlItr, limit: 50} - err = engine.PrettyPrintResults(ctx, engine.FormatTabular, sqlSch, limitItr, false, false, false) + err = engine.PrettyPrintResults(ctx, engine.FormatTabular, sqlSch, limitItr, false, false, false, false) if err != nil { return errhand.BuildDError("Error outputting rows").AddCause(err).Build() } diff --git a/go/cmd/dolt/commands/engine/sql_print.go b/go/cmd/dolt/commands/engine/sql_print.go index ba715a62b7..1b59398215 100644 --- a/go/cmd/dolt/commands/engine/sql_print.go +++ b/go/cmd/dolt/commands/engine/sql_print.go @@ -23,6 +23,7 @@ import ( "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/types" + "github.com/dolthub/vitess/go/sqltypes" "github.com/fatih/color" "github.com/dolthub/dolt/go/cmd/dolt/cli" @@ -56,16 +57,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, pageResults, showWarnings, printOkResult bool) (rerr error) { - return prettyPrintResultsWithSummary(ctx, resultFormat, sqlSch, rowIter, PrintNoSummary, pageResults, showWarnings, printOkResult) +func PrettyPrintResults(ctx *sql.Context, resultFormat PrintResultFormat, sqlSch sql.Schema, rowIter sql.RowIter, pageResults, showWarnings, printOkResult, binaryAsHex bool) (rerr error) { + return prettyPrintResultsWithSummary(ctx, resultFormat, sqlSch, rowIter, PrintNoSummary, pageResults, showWarnings, printOkResult, binaryAsHex) } // 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, pageResults, showWarnings, printOkResult bool) (rerr error) { - return prettyPrintResultsWithSummary(ctx, resultFormat, sqlSch, rowIter, PrintRowCountAndTiming, pageResults, showWarnings, printOkResult) +func PrettyPrintResultsExtended(ctx *sql.Context, resultFormat PrintResultFormat, sqlSch sql.Schema, rowIter sql.RowIter, pageResults, showWarnings, printOkResult, binaryAsHex bool) (rerr error) { + return prettyPrintResultsWithSummary(ctx, resultFormat, sqlSch, rowIter, PrintRowCountAndTiming, pageResults, showWarnings, printOkResult, binaryAsHex) } -func prettyPrintResultsWithSummary(ctx *sql.Context, resultFormat PrintResultFormat, sqlSch sql.Schema, rowIter sql.RowIter, summary PrintSummaryBehavior, pageResults, showWarnings, printOkResult bool) (rerr error) { +func prettyPrintResultsWithSummary(ctx *sql.Context, resultFormat PrintResultFormat, sqlSch sql.Schema, rowIter sql.RowIter, summary PrintSummaryBehavior, pageResults, showWarnings, printOkResult, binaryAsHex bool) (rerr error) { defer func() { closeErr := rowIter.Close(ctx) if rerr == nil && closeErr != nil { @@ -126,6 +127,11 @@ func prettyPrintResultsWithSummary(ctx *sql.Context, resultFormat PrintResultFor } } + // Wrap iterator with binary-to-hex transformation if needed + if binaryAsHex { + rowIter = newBinaryHexIterator(rowIter, sqlSch) + } + numRows, err = writeResultSet(ctx, rowIter, wr) } @@ -197,6 +203,55 @@ func printResultSetSummary(numRows int, numWarnings uint16, warningsList string, return nil } +// binaryHexIterator wraps a row iterator and transforms binary data to hex format +type binaryHexIterator struct { + inner sql.RowIter + schema sql.Schema +} + +// newBinaryHexIterator creates a new iterator that transforms binary data to hex format. +// It wraps the provided iterator and transforms BINARY and VARBINARY column values +// to hex string representation (e.g., "0x41424344"). +func newBinaryHexIterator(inner sql.RowIter, schema sql.Schema) sql.RowIter { + return &binaryHexIterator{ + inner: inner, + schema: schema, + } +} + +// Next returns the next row from the wrapped iterator with binary data transformed to hex format. +// Binary and VarBinary column values are converted to uppercase hex strings prefixed with "0x". +func (iter *binaryHexIterator) Next(ctx *sql.Context) (sql.Row, error) { + row, err := iter.inner.Next(ctx) + if err != nil { + return nil, err + } + + // Transform binary column values to hex string format in place + // Currently supports BINARY and VARBINARY types only. + // TODO: Add support for BLOB types (TINYBLOB, BLOB, MEDIUMBLOB, LONGBLOB) and BIT type + // as confirmed by testing MySQL 8.4+ binary-as-hex behavior + for i, val := range row { + if val != nil && i < len(iter.schema) { + switch iter.schema[i].Type.Type() { + case sqltypes.Binary, sqltypes.VarBinary: + if bytes, ok := val.([]byte); ok { + row[i] = fmt.Sprintf("0x%X", bytes) + } else { + row[i] = fmt.Sprintf("0x%X", []byte(fmt.Sprint(val))) + } + } + } + } + + return row, nil +} + +// Close closes the wrapped iterator and releases any resources. +func (iter *binaryHexIterator) Close(ctx *sql.Context) error { + return iter.inner.Close(ctx) +} + // writeResultSet drains the iterator given, printing rows from it to the writer given. Returns the number of rows. func writeResultSet(ctx *sql.Context, rowIter sql.RowIter, wr table.SqlRowWriter) (int, error) { i := 0 diff --git a/go/cmd/dolt/commands/schcmds/tags.go b/go/cmd/dolt/commands/schcmds/tags.go index a790d1ae3c..f5fcf23bd3 100644 --- a/go/cmd/dolt/commands/schcmds/tags.go +++ b/go/cmd/dolt/commands/schcmds/tags.go @@ -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...), false, false, false) + err = engine.PrettyPrintResults(sqlCtx, outputFmt, headerSchema, sql.RowsToRowIter(rows...), false, false, false, false) return commands.HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage) } diff --git a/go/cmd/dolt/commands/sql.go b/go/cmd/dolt/commands/sql.go index c9888b4c6d..f1df2c5a99 100644 --- a/go/cmd/dolt/commands/sql.go +++ b/go/cmd/dolt/commands/sql.go @@ -112,52 +112,6 @@ func init() { dsqle.AddDoltSystemVariables() } -// binaryAsHexRowIter wraps a row iterator and converts binary data to hex format -type binaryAsHexRowIter struct { - inner sql.RowIter - schema sql.Schema -} - -func newBinaryAsHexRowIter(inner sql.RowIter, schema sql.Schema) sql.RowIter { - return &binaryAsHexRowIter{ - inner: inner, - schema: schema, - } -} - -func (iter *binaryAsHexRowIter) Next(ctx *sql.Context) (sql.Row, error) { - row, err := iter.inner.Next(ctx) - if err != nil { - return nil, err - } - - // Transform binary data to hex format - for i, val := range row { - if val != nil && i < len(iter.schema) { - if stringType, ok := iter.schema[i].Type.(sql.StringType); ok { - sqlType := stringType.Type() - switch sqlType { - case sqltypes.Binary, sqltypes.VarBinary: - // Handle byte slice for VARBINARY/BINARY columns (local connections) - if binaryBytes, ok := val.([]byte); ok { - row[i] = fmt.Sprintf("0x%X", binaryBytes) - } else if binaryString, ok := val.(string); ok { - // Handle string data that contains binary (server connections) - row[i] = fmt.Sprintf("0x%X", []byte(binaryString)) - } - } - } - } - } - - return row, nil -} - -func (iter *binaryAsHexRowIter) Close(ctx *sql.Context) error { - return iter.inner.Close(ctx) -} - - type SqlCmd struct { VersionStr string } @@ -197,8 +151,8 @@ func (cmd SqlCmd) ArgParser() *argparser.ArgParser { ap.SupportsFlag(continueFlag, "c", "Continue running queries on an error. Used for batch mode only.") ap.SupportsString(fileInputFlag, "f", "input file", "Execute statements from the file given.") ap.SupportsFlag(binaryAsHexFlag, "", "Print binary data as hex. Enabled by default for interactive terminals.") - // --skip-binary-as-hex is supported but not shown in help, matching MySQL's behavior - ap.SupportsFlag(skipBinaryAsHexFlag, "", "") + // TODO: MySQL uses a skip- pattern for negating flags and doesn't show them in help + ap.SupportsFlag(skipBinaryAsHexFlag, "", "Disable binary data as hex output.") return ap } @@ -265,19 +219,8 @@ func (cmd SqlCmd) Exec(ctx context.Context, commandStr string, args []string, dE return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage) } - // Determine if we're running in a TTY (used by all modes) - isTty := false - fi, err := os.Stdin.Stat() - if err != nil { - if !osutil.IsWindows { - return HandleVErrAndExitCode(errhand.BuildDError("Couldn't stat STDIN. This is a bug.").Build(), usage) - } - } else { - isTty = fi.Mode()&os.ModeCharDevice != 0 - } - - // Determine binary-as-hex behavior once (default based on TTY, flags can override) - binaryAsHex := isTty + // Determine binary-as-hex behavior from flags (default false for non-interactive modes) + binaryAsHex := false if apr.Contains(skipBinaryAsHexFlag) { binaryAsHex = false } else if apr.Contains(binaryAsHexFlag) { @@ -303,6 +246,15 @@ func (cmd SqlCmd) Exec(ctx context.Context, commandStr string, args []string, dE return listSavedQueries(sqlCtx, queryist, dEnv, format, usage) } else { // Run in either batch mode for piped input, or shell mode for interactive + isTty := false + fi, err := os.Stdin.Stat() + if err != nil { + if !osutil.IsWindows { + return sqlHandleVErrAndExitCode(queryist, errhand.BuildDError("Couldn't stat STDIN. This is a bug.").Build(), usage) + } + } else { + isTty = fi.Mode()&os.ModeCharDevice != 0 + } _, continueOnError := apr.GetValue(continueFlag) @@ -326,7 +278,9 @@ func (cmd SqlCmd) Exec(ctx context.Context, commandStr string, args []string, dE } if isTty { - err := execShell(sqlCtx, queryist, format, cliCtx, binaryAsHex) + // In shell mode, default to hex format unless explicitly disabled + shellBinaryAsHex := !apr.Contains(skipBinaryAsHexFlag) + err := execShell(sqlCtx, queryist, format, cliCtx, shellBinaryAsHex) if err != nil { return sqlHandleVErrAndExitCode(queryist, errhand.VerboseErrorFromError(err), usage) } @@ -513,10 +467,7 @@ func execSingleQuery( if rowIter != nil { // Apply binary-as-hex formatting if enabled - if binaryAsHex { - rowIter = newBinaryAsHexRowIter(rowIter, sqlSch) - } - err = engine.PrettyPrintResults(sqlCtx, format, sqlSch, rowIter, false, false, false) + err = engine.PrettyPrintResults(sqlCtx, format, sqlSch, rowIter, false, false, false, binaryAsHex) if err != nil { return errhand.VerboseErrorFromError(err) } @@ -732,11 +683,7 @@ func execBatchMode(ctx *sql.Context, qryist cli.Queryist, input io.Reader, conti fileReadProg.printNewLineIfNeeded() } } - // Apply binary-as-hex formatting if enabled - if binaryAsHex { - rowIter = newBinaryAsHexRowIter(rowIter, sqlSch) - } - err = engine.PrettyPrintResults(ctx, format, sqlSch, rowIter, false, false, false) + err = engine.PrettyPrintResults(ctx, format, sqlSch, rowIter, false, false, false, binaryAsHex) if err != nil { err = buildBatchSqlErr(scanner.state.statementStartLine, query, err) if !continueOnErr { @@ -917,15 +864,11 @@ func execShell(sqlCtx *sql.Context, qryist cli.Queryist, format engine.PrintResu verr := formatQueryError("", err) shell.Println(verr.Verbose()) } else if rowIter != nil { - // Apply binary-as-hex formatting to row data if enabled - if binaryAsHex { - rowIter = newBinaryAsHexRowIter(rowIter, sqlSch) - } switch closureFormat { case engine.FormatTabular, engine.FormatVertical: - err = engine.PrettyPrintResultsExtended(sqlCtx, closureFormat, sqlSch, rowIter, pagerEnabled, toggleWarnings, true) + err = engine.PrettyPrintResultsExtended(sqlCtx, closureFormat, sqlSch, rowIter, pagerEnabled, toggleWarnings, true, binaryAsHex) default: - err = engine.PrettyPrintResults(sqlCtx, closureFormat, sqlSch, rowIter, pagerEnabled, toggleWarnings, true) + err = engine.PrettyPrintResults(sqlCtx, closureFormat, sqlSch, rowIter, pagerEnabled, toggleWarnings, true, binaryAsHex) } } else { if _, isUseStmt := sqlStmt.(*sqlparser.Use); isUseStmt { diff --git a/go/cmd/dolt/commands/sqlserver/queryist_utils.go b/go/cmd/dolt/commands/sqlserver/queryist_utils.go index 0129a7145b..ec04c6cde1 100644 --- a/go/cmd/dolt/commands/sqlserver/queryist_utils.go +++ b/go/cmd/dolt/commands/sqlserver/queryist_utils.go @@ -23,8 +23,6 @@ import ( "strings" "github.com/dolthub/go-mysql-server/sql" - "github.com/dolthub/go-mysql-server/sql/types" - "github.com/dolthub/vitess/go/sqltypes" "github.com/dolthub/vitess/go/vt/sqlparser" "github.com/go-sql-driver/mysql" "github.com/gocraft/dbr/v2" @@ -33,11 +31,11 @@ import ( "github.com/dolthub/dolt/go/cmd/dolt/cli" "github.com/dolthub/dolt/go/libraries/doltcore/servercfg" "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" + "github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlutil" "github.com/dolthub/dolt/go/libraries/utils/argparser" "github.com/dolthub/dolt/go/libraries/utils/filesys" ) - // BuildConnectionStringQueryist returns a Queryist that connects to the server specified by the given server config. Presence in this // module isn't ideal, but it's the only way to get the server config into the queryist. func BuildConnectionStringQueryist(ctx context.Context, cwdFS filesys.Filesys, creds *cli.UserPassword, apr *argparser.ArgParseResults, host string, port int, useTLS bool, dbRev string) (cli.LateBindQueryist, error) { @@ -158,20 +156,9 @@ func NewMysqlRowWrapper(sqlRows *sql2.Rows) (*MysqlRowWrapper, error) { iRow := make([]interface{}, len(colTypes)) rows := make([]sql.Row, 0) for i, colType := range colTypes { - // Check if this is a binary type by examining the database type name - typeName := strings.ToLower(colType.DatabaseTypeName()) - var sqlType sql.Type - switch typeName { - case "binary", "varbinary", "tinyblob", "blob", "mediumblob", "longblob": - sqlType = types.MustCreateBinary(sqltypes.VarBinary, 255) - default: - // Default to LongText for all other types (as was done before) - sqlType = types.LongText - } - schema[i] = &sql.Column{ Name: colType.Name(), - Type: sqlType, + Type: sqlutil.DatabaseTypeNameToSqlType(colType.DatabaseTypeName()), Nullable: true, } iRow[i] = &vRow[i] diff --git a/go/libraries/doltcore/sqle/sqlutil/sql_row.go b/go/libraries/doltcore/sqle/sqlutil/sql_row.go index f8b3a99400..4abed78b47 100644 --- a/go/libraries/doltcore/sqle/sqlutil/sql_row.go +++ b/go/libraries/doltcore/sqle/sqlutil/sql_row.go @@ -18,10 +18,14 @@ import ( "context" "errors" "fmt" + "strings" "github.com/dolthub/go-mysql-server/sql" + gmstypes "github.com/dolthub/go-mysql-server/sql/types" + // Necessary for the empty context used by some functions to be initialized with system vars _ "github.com/dolthub/go-mysql-server/sql/variables" + "github.com/dolthub/vitess/go/sqltypes" "github.com/dolthub/dolt/go/libraries/doltcore/row" "github.com/dolthub/dolt/go/libraries/doltcore/schema" @@ -248,3 +252,21 @@ func SqlColToStr(ctx *sql.Context, sqlType sql.Type, col interface{}) (string, e return "", nil } + +// DatabaseTypeNameToSqlType converts a MySQL wire protocol database type name +// to a go-mysql-server sql.Type. This uses the same type mapping logic as the existing +// Dolt type system for consistency. +// TODO: Add support for BLOB types (TINYBLOB, BLOB, MEDIUMBLOB, LONGBLOB) and BIT type +// as confirmed by testing MySQL 8.4+ binary-as-hex behavior +func DatabaseTypeNameToSqlType(databaseTypeName string) sql.Type { + typeName := strings.ToLower(databaseTypeName) + switch typeName { + case "binary": + return gmstypes.MustCreateBinary(sqltypes.Binary, 255) + case "varbinary": + return gmstypes.MustCreateBinary(sqltypes.VarBinary, 255) + default: + // Default to LongText for all other types (as was done before) + return gmstypes.LongText + } +} diff --git a/go/libraries/doltcore/table/untyped/tabular/fixedwidth_tablewriter.go b/go/libraries/doltcore/table/untyped/tabular/fixedwidth_tablewriter.go index de9d9863f2..6b72cb0311 100644 --- a/go/libraries/doltcore/table/untyped/tabular/fixedwidth_tablewriter.go +++ b/go/libraries/doltcore/table/untyped/tabular/fixedwidth_tablewriter.go @@ -32,7 +32,6 @@ import ( const writeBufSize = 256 * 1024 - // FixedWidthTableWriter is a TableWriter that applies a fixed width transform to its fields. All fields are // expected to be strings. type FixedWidthTableWriter struct { diff --git a/integration-tests/bats/binary-as-hex-flag-precedence-interactive.expect b/integration-tests/bats/binary-as-hex-flag-precedence-interactive.expect new file mode 100644 index 0000000000..16a0d75ee3 --- /dev/null +++ b/integration-tests/bats/binary-as-hex-flag-precedence-interactive.expect @@ -0,0 +1,46 @@ +#!/usr/bin/expect + +set port [lindex $argv 0] +set timeout 10 + +spawn dolt --host 127.0.0.1 --port $port --no-tls sql --binary-as-hex --skip-binary-as-hex +expect ">" + +send "USE repo1;\r" +expect ">" + +send "SELECT pk, vb FROM binary_test WHERE pk = 2;\r" +expect { + "0x616263" { + send "exit\r" + expect eof + exit 1 + } + "abc" { + expect ">" + send "SELECT pk, vb FROM binary_test WHERE pk = 1;\r" + expect { + "0x0A000000" { + send "exit\r" + expect eof + exit 1 + } + -re "1.*\n" { + expect ">" + send "exit\r" + expect eof + exit 0 + } + timeout { + send "exit\r" + expect eof + exit 1 + } + } + } + timeout { + send "exit\r" + expect eof + exit 1 + } +} \ No newline at end of file diff --git a/integration-tests/bats/binary-as-hex-server-default-interactive.expect b/integration-tests/bats/binary-as-hex-server-default-interactive.expect new file mode 100644 index 0000000000..5bdac7c22a --- /dev/null +++ b/integration-tests/bats/binary-as-hex-server-default-interactive.expect @@ -0,0 +1,41 @@ +#!/usr/bin/expect + +set port [lindex $argv 0] +set timeout 10 + +spawn dolt --host 127.0.0.1 --port $port --no-tls sql +expect ">" + +send "USE repo1;\r" +expect ">" + +send "SELECT pk, vb FROM binary_test WHERE pk = 1;\r" +expect { + "0x0A000000" { + expect ">" + send "SELECT pk, vb FROM binary_test WHERE pk = 2;\r" + expect { + "0x616263" { + expect ">" + send "exit\r" + expect eof + exit 0 + } + "abc" { + send "exit\r" + expect eof + exit 1 + } + timeout { + send "exit\r" + expect eof + exit 1 + } + } + } + timeout { + send "exit\r" + expect eof + exit 1 + } +} \ No newline at end of file diff --git a/integration-tests/bats/binary-as-hex-server-interactive.expect b/integration-tests/bats/binary-as-hex-server-interactive.expect index 62ad47a837..e74498206c 100755 --- a/integration-tests/bats/binary-as-hex-server-interactive.expect +++ b/integration-tests/bats/binary-as-hex-server-interactive.expect @@ -9,14 +9,29 @@ expect ">" send "USE repo1;\r" expect ">" -send "SELECT * FROM binary_test;\r" +send "SELECT pk, vb FROM binary_test WHERE pk = 2;\r" expect { - "0x0A000000" { - # Found hex output - this is correct + "0x616263" { expect ">" + send "SELECT pk, vb FROM binary_test WHERE pk = 1;\r" + expect { + "0x0A000000" { + expect ">" + send "exit\r" + expect eof + exit 0 + } + timeout { + send "exit\r" + expect eof + exit 1 + } + } + } + "abc" { send "exit\r" expect eof - exit 0 + exit 1 } timeout { send "exit\r" diff --git a/integration-tests/bats/binary-as-hex-skip-flag-interactive.expect b/integration-tests/bats/binary-as-hex-skip-flag-interactive.expect index dac896897c..423e093f57 100755 --- a/integration-tests/bats/binary-as-hex-skip-flag-interactive.expect +++ b/integration-tests/bats/binary-as-hex-skip-flag-interactive.expect @@ -9,14 +9,34 @@ expect ">" send "USE repo1;\r" expect ">" -send "SELECT * FROM binary_test;\r" +send "SELECT pk, vb FROM binary_test WHERE pk = 2;\r" expect { - -re "\\| 1 \\|.*\\|" { - # Found raw binary output (not hex) - this is correct for skip flag - expect ">" + "0x616263" { send "exit\r" expect eof - exit 0 + exit 1 + } + "abc" { + expect ">" + send "SELECT pk, vb FROM binary_test WHERE pk = 1;\r" + expect { + "0x0A000000" { + send "exit\r" + expect eof + exit 1 + } + -re "1.*\n" { + expect ">" + send "exit\r" + expect eof + exit 0 + } + timeout { + send "exit\r" + expect eof + exit 1 + } + } } timeout { send "exit\r" diff --git a/integration-tests/bats/sql-server.bats b/integration-tests/bats/sql-server.bats index 341e74298b..56940f1117 100644 --- a/integration-tests/bats/sql-server.bats +++ b/integration-tests/bats/sql-server.bats @@ -2199,7 +2199,7 @@ EOF } -@test "sql-server: client interactive binary-as-hex behavior works with server connections" { +@test "sql-server: client binary-as-hex behavior works with server connections" { skiponwindows "Missing dependencies" which expect > /dev/null || skip "expect not available" @@ -2212,16 +2212,59 @@ EOF start_sql_server repo1 - # Test --binary-as-hex flag in interactive mode (should show hex) - run expect "$BATS_TEST_DIRNAME/binary-as-hex-server-interactive.expect" $PORT - echo "EXPECT OUTPUT: $output" - echo "EXPECT STATUS: $status" + # 1. Test default interactive behavior (should show hex by default) + run expect "$BATS_TEST_DIRNAME/binary-as-hex-server-default-interactive.expect" $PORT [ $status -eq 0 ] - # Test --skip-binary-as-hex flag in interactive mode (should show raw) + # 2. Test --binary-as-hex flag in interactive mode (should show hex) + run expect "$BATS_TEST_DIRNAME/binary-as-hex-server-interactive.expect" $PORT + [ $status -eq 0 ] + + # 3. Test --skip-binary-as-hex flag in interactive mode (should show raw) run expect "$BATS_TEST_DIRNAME/binary-as-hex-skip-flag-interactive.expect" $PORT [ $status -eq 0 ] + # 4. Test flag precedence: --skip-binary-as-hex overrides --binary-as-hex + run expect "$BATS_TEST_DIRNAME/binary-as-hex-flag-precedence-interactive.expect" $PORT + [ $status -eq 0 ] + + # 5. Test non-interactive server behavior with -q flag (should show raw by default) + run dolt --host 127.0.0.1 --port $PORT --no-tls sql -q "USE repo1; SELECT vb FROM binary_test WHERE pk = 1" + [ $status -eq 0 ] + ! [[ "$output" =~ "0x0A000000" ]] || false + + # 6. Test non-interactive server behavior with --binary-as-hex flag + run dolt --host 127.0.0.1 --port $PORT --no-tls sql --binary-as-hex -q "USE repo1; SELECT vb FROM binary_test WHERE pk = 1" + [ $status -eq 0 ] + [[ "$output" =~ "0x0A000000" ]] || false + + # 7. Test non-interactive server behavior with printable data + run dolt --host 127.0.0.1 --port $PORT --no-tls sql -q "USE repo1; SELECT vb FROM binary_test WHERE pk = 2" + [ $status -eq 0 ] + [[ "$output" =~ "abc" ]] || false + + # 8. Test non-interactive server flag precedence + run dolt --host 127.0.0.1 --port $PORT --no-tls sql --binary-as-hex --skip-binary-as-hex -q "USE repo1; SELECT vb FROM binary_test WHERE pk = 2" + [ $status -eq 0 ] + [[ "$output" =~ "abc" ]] || false + ! [[ "$output" =~ "0x616263" ]] || false + + # 9. Test non-interactive server behavior with -q flag (should show raw by default) + run dolt --host 127.0.0.1 --port $PORT --no-tls sql -q "USE repo1; SELECT vb FROM binary_test WHERE pk = 1" + [ $status -eq 0 ] + ! [[ "$output" =~ "0x0A000000" ]] || false + + # 10. Test non-interactive server behavior with -q and --binary-as-hex flags + run dolt --host 127.0.0.1 --port $PORT --no-tls sql --binary-as-hex -q "USE repo1; SELECT vb FROM binary_test WHERE pk = 1" + [ $status -eq 0 ] + [[ "$output" =~ "0x0A000000" ]] || false + + # 11. Test non-interactive server -q flag precedence + run dolt --host 127.0.0.1 --port $PORT --no-tls sql --binary-as-hex --skip-binary-as-hex -q "USE repo1; SELECT vb FROM binary_test WHERE pk = 2" + [ $status -eq 0 ] + [[ "$output" =~ "abc" ]] || false + ! [[ "$output" =~ "0x616263" ]] || false + stop_sql_server 1 }