Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ switches are most important to you to have implemented next in the new sqlcmd.
- `:Connect` now has an optional `-G` parameter to select one of the authentication methods for Azure SQL Database - `SqlAuthentication`, `ActiveDirectoryDefault`, `ActiveDirectoryIntegrated`, `ActiveDirectoryServicePrincipal`, `ActiveDirectoryManagedIdentity`, `ActiveDirectoryPassword`. If `-G` is not provided, either Integrated security or SQL Authentication will be used, dependent on the presence of a `-U` username parameter.
- The new `--driver-logging-level` command line parameter allows you to see traces from the `go-mssqldb` client driver. Use `64` to see all traces.
- Sqlcmd can now print results using a vertical format. Use the new `--vertical` command line option to set it. It's also controlled by the `SQLCMDFORMAT` scripting variable.
- The `-j` (or `--raw-errors`) flag prints raw error messages, without the `Msg`, `Level`, `State`, `Server`, and `Line` prefix that is normally prepended to SQL Server error messages.

```
1> select session_id, client_interface_name, program_name from sys.dm_exec_sessions where session_id=@@spid
Expand Down
5 changes: 4 additions & 1 deletion cmd/sqlcmd/sqlcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ type SQLCmdArguments struct {
ChangePassword string
ChangePasswordAndExit string
TraceFile string
// RawErrors prints only the error message without Msg/Level/State header
RawErrors bool
// Keep Help at the end of the list
Help bool
}
Expand Down Expand Up @@ -452,6 +454,7 @@ func setFlags(rootCmd *cobra.Command, args *SQLCmdArguments) {
rootCmd.Flags().IntVar(&args.DriverLoggingLevel, "driver-logging-level", 0, localizer.Sprintf("Level of mssql driver messages to print"))
rootCmd.Flags().BoolVarP(&args.ExitOnError, "exit-on-error", "b", false, localizer.Sprintf("Specifies that sqlcmd exits and returns a %s value when an error occurs", localizer.DosErrorLevel))
rootCmd.Flags().IntVarP(&args.ErrorLevel, "error-level", "m", 0, localizer.Sprintf("Controls which error messages are sent to %s. Messages that have severity level greater than or equal to this level are sent", localizer.StdoutName))
rootCmd.Flags().BoolVarP(&args.RawErrors, "raw-errors", "j", false, localizer.Sprintf("Prints raw error messages with no additional information"))

//Need to decide on short of Header , as "h" is already used in help command in Cobra
rootCmd.Flags().IntVarP(&args.Headers, "headers", "h", 0, localizer.Sprintf("Specifies the number of rows to print between the column headings. Use -h-1 to specify that headers not be printed"))
Expand Down Expand Up @@ -828,7 +831,7 @@ func run(vars *sqlcmd.Variables, args *SQLCmdArguments) (int, error) {
}

s.Connect = &connectConfig
s.Format = sqlcmd.NewSQLCmdDefaultFormatter(args.TrimSpaces, args.getControlCharacterBehavior())
s.Format = sqlcmd.NewSQLCmdDefaultFormatter(args.TrimSpaces, args.getControlCharacterBehavior(), args.RawErrors)
if args.OutputFile != "" {
err = s.RunCommand(s.Cmd["OUT"], []string{args.OutputFile})
if err != nil {
Expand Down
10 changes: 10 additions & 0 deletions cmd/sqlcmd/sqlcmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ func TestValidCommandLineToArgsConversion(t *testing.T) {
{[]string{"-N", "true", "-J", "/path/to/cert2.pem"}, func(args SQLCmdArguments) bool {
return args.EncryptConnection == "true" && args.ServerCertificate == "/path/to/cert2.pem"
}},
// Test -j flag for raw error messages
{[]string{"-j"}, func(args SQLCmdArguments) bool {
return args.RawErrors
}},
{[]string{"--raw-errors"}, func(args SQLCmdArguments) bool {
return args.RawErrors
}},
{[]string{"-j", "-b"}, func(args SQLCmdArguments) bool {
return args.RawErrors && args.ExitOnError
}},
}

for _, test := range commands {
Expand Down
2 changes: 1 addition & 1 deletion internal/sql/mssql.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (m *mssql) Connect(
m.console = nil
}
m.sqlcmd = sqlcmd.New(m.console, "", v)
m.sqlcmd.Format = sqlcmd.NewSQLCmdDefaultFormatter(false, sqlcmd.ControlIgnore)
m.sqlcmd.Format = sqlcmd.NewSQLCmdDefaultFormatter(false, sqlcmd.ControlIgnore, false)
connect := sqlcmd.ConnectSettings{
ServerName: fmt.Sprintf(
"%s,%#v",
Expand Down
2 changes: 1 addition & 1 deletion pkg/sqlcmd/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ func TestListCommandUsesColorizer(t *testing.T) {
func TestListColorPrintsStyleSamples(t *testing.T) {
vars := InitializeVariables(false)
s := New(nil, "", vars)
s.Format = NewSQLCmdDefaultFormatter(false, ControlIgnore)
s.Format = NewSQLCmdDefaultFormatter(false, ControlIgnore, false)
// force colorizer on
s.colorizer = color.New(true)
buf := &memoryBuffer{buf: new(bytes.Buffer)}
Expand Down
15 changes: 10 additions & 5 deletions pkg/sqlcmd/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,17 @@ type sqlCmdFormatterType struct {
maxColNameLen int
colorizer color.Colorizer
xml bool
rawErrors bool
}

// NewSQLCmdDefaultFormatter returns a Formatter that mimics the original ODBC-based sqlcmd formatter
func NewSQLCmdDefaultFormatter(removeTrailingSpaces bool, ccb ControlCharacterBehavior) Formatter {
func NewSQLCmdDefaultFormatter(removeTrailingSpaces bool, ccb ControlCharacterBehavior, rawErrors bool) Formatter {
return &sqlCmdFormatterType{
removeTrailingSpaces: removeTrailingSpaces,
format: "horizontal",
colorizer: color.New(false),
ccb: ccb,
rawErrors: rawErrors,
}
}

Expand Down Expand Up @@ -223,10 +225,13 @@ func (f *sqlCmdFormatterType) AddError(err error) {
switch e := (err).(type) {
case mssql.Error:
if print = f.vars.ErrorLevel() <= 0 || e.Class >= uint8(f.vars.ErrorLevel()); print {
if len(e.ProcName) > 0 {
b.WriteString(localizer.Sprintf("Msg %#v, Level %d, State %d, Server %s, Procedure %s, Line %#v%s", e.Number, e.Class, e.State, e.ServerName, e.ProcName, e.LineNo, SqlcmdEol))
} else {
b.WriteString(localizer.Sprintf("Msg %#v, Level %d, State %d, Server %s, Line %#v%s", e.Number, e.Class, e.State, e.ServerName, e.LineNo, SqlcmdEol))
// Only print the structured error header if rawErrors mode is not enabled
if !f.rawErrors {
if len(e.ProcName) > 0 {
b.WriteString(localizer.Sprintf("Msg %#v, Level %d, State %d, Server %s, Procedure %s, Line %#v%s", e.Number, e.Class, e.State, e.ServerName, e.ProcName, e.LineNo, SqlcmdEol))
} else {
b.WriteString(localizer.Sprintf("Msg %#v, Level %d, State %d, Server %s, Line %#v%s", e.Number, e.Class, e.State, e.ServerName, e.LineNo, SqlcmdEol))
}
}
msg = strings.TrimPrefix(msg, "mssql: ")
}
Expand Down
42 changes: 42 additions & 0 deletions pkg/sqlcmd/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"testing"

mssql "github.com/microsoft/go-mssqldb"
"github.com/microsoft/go-sqlcmd/internal/color"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -158,3 +159,44 @@ func TestFormatterXmlMode(t *testing.T) {
assert.NoError(t, err, "runSqlCmd returned error")
assert.Equal(t, `<sys.databases name="master"/>`+SqlcmdEol, buf.buf.String())
}

func TestFormatterRawErrors(t *testing.T) {
// Test that raw errors mode only prints the error message without the Msg/Level/State header
vars := InitializeVariables(false)
errBuf := new(strings.Builder)

// Create formatter with rawErrors = false (default)
f := NewSQLCmdDefaultFormatter(false, ControlIgnore, false).(*sqlCmdFormatterType)
f.BeginBatch("", vars, new(strings.Builder), errBuf)

// Create a mssql.Error to test with
testErr := mssql.Error{
Number: 208,
Class: 16,
State: 1,
ServerName: "testserver",
Message: "Invalid object name 'nonexistent'.",
}

f.AddError(testErr)
normalOutput := errBuf.String()
// Normal mode should include the Msg header
assert.Contains(t, normalOutput, "Msg 208")
assert.Contains(t, normalOutput, "Level 16")
assert.Contains(t, normalOutput, "State 1")
assert.Contains(t, normalOutput, "Invalid object name 'nonexistent'.")

// Create formatter with rawErrors = true
errBuf.Reset()
f = NewSQLCmdDefaultFormatter(false, ControlIgnore, true).(*sqlCmdFormatterType)
f.BeginBatch("", vars, new(strings.Builder), errBuf)

f.AddError(testErr)
rawOutput := errBuf.String()
// Raw mode should NOT include the Msg header
assert.NotContains(t, rawOutput, "Msg 208")
assert.NotContains(t, rawOutput, "Level 16")
assert.NotContains(t, rawOutput, "State 1")
// But should still contain the actual error message
assert.Contains(t, rawOutput, "Invalid object name 'nonexistent'.")
}
Comment thread
dlevy-msft-sql marked this conversation as resolved.
6 changes: 3 additions & 3 deletions pkg/sqlcmd/sqlcmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ func setupSqlCmdWithMemoryOutput(t testing.TB) (*Sqlcmd, *memoryBuffer) {
v.Set(SQLCMDMAXVARTYPEWIDTH, "0")
s := New(nil, "", v)
s.Connect = newConnect(t)
s.Format = NewSQLCmdDefaultFormatter(true, ControlIgnore)
s.Format = NewSQLCmdDefaultFormatter(true, ControlIgnore, false)
buf := &memoryBuffer{buf: new(bytes.Buffer)}
s.SetOutput(buf)
err := s.ConnectDb(nil, true)
Expand All @@ -633,7 +633,7 @@ func setupSqlcmdWithFileOutput(t testing.TB) (*Sqlcmd, *os.File) {
v.Set(SQLCMDMAXVARTYPEWIDTH, "0")
s := New(nil, "", v)
s.Connect = newConnect(t)
s.Format = NewSQLCmdDefaultFormatter(true, ControlIgnore)
s.Format = NewSQLCmdDefaultFormatter(true, ControlIgnore, false)
file, err := os.CreateTemp("", "sqlcmdout")
assert.NoError(t, err, "os.CreateTemp")
s.SetOutput(file)
Expand All @@ -651,7 +651,7 @@ func setupSqlcmdWithFileErrorOutput(t testing.TB) (*Sqlcmd, *os.File, *os.File)
v.Set(SQLCMDMAXVARTYPEWIDTH, "0")
s := New(nil, "", v)
s.Connect = newConnect(t)
s.Format = NewSQLCmdDefaultFormatter(true, ControlIgnore)
s.Format = NewSQLCmdDefaultFormatter(true, ControlIgnore, false)
outfile, err := os.CreateTemp("", "sqlcmdout")
assert.NoError(t, err, "os.CreateTemp")
errfile, err := os.CreateTemp("", "sqlcmderr")
Expand Down
Loading