Skip to content
Open
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
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
103 changes: 103 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,105 @@ 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'.")
}

func TestFormatterErrorWithProcName(t *testing.T) {
// Test that errors with ProcName include the Procedure in the 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 with ProcName to test with
testErr := mssql.Error{
Number: 50000,
Class: 16,
State: 1,
ServerName: "testserver",
ProcName: "myStoredProc",
LineNo: 10,
Message: "Error raised from stored procedure.",
}

f.AddError(testErr)
output := errBuf.String()
// Should include the Procedure in the header
assert.Contains(t, output, "Msg 50000")
assert.Contains(t, output, "Level 16")
assert.Contains(t, output, "State 1")
assert.Contains(t, output, "Server testserver")
assert.Contains(t, output, "Procedure myStoredProc")
assert.Contains(t, output, "Line 10")
assert.Contains(t, output, "Error raised from stored procedure.")
}

func TestFormatterErrorWithProcNameRawMode(t *testing.T) {
// Test that errors with ProcName in raw mode skip header but still print message
vars := InitializeVariables(false)
errBuf := new(strings.Builder)

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

testErr := mssql.Error{
Number: 50000,
Class: 16,
State: 1,
ServerName: "testserver",
ProcName: "myStoredProc",
LineNo: 10,
Message: "Error raised from stored procedure.",
}

f.AddError(testErr)
output := errBuf.String()
// Raw mode should NOT include the header
assert.NotContains(t, output, "Msg 50000")
assert.NotContains(t, output, "Level 16")
assert.NotContains(t, output, "Procedure myStoredProc")
// But should still contain the actual error message
assert.Contains(t, output, "Error raised from stored procedure.")
}
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