Skip to content
Closed
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
60 changes: 60 additions & 0 deletions cmd/sqlcmd/sqlcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ func Execute(version string) {
// We need to rewrite the arguments to add -i and -v in front of each space-delimited value to be Cobra-friendly.
// For flags like -r we need to inject the default value if the user omits it
func convertOsArgs(args []string) (cargs []string) {
// Pre-process to handle special help flag cases
args = preprocessHelpFlags(args)

flag := ""
first := true
for i, a := range args {
Expand Down Expand Up @@ -323,6 +326,63 @@ func convertOsArgs(args []string) (cargs []string) {
return
}

// preprocessHelpFlags handles special cases for help flags to improve user experience:
// 1. Converts "-help" to "--help" since "-help" would be parsed as "-h elp"
// 2. Converts "-h" without an argument to "-?" to show help, since many users expect
// "-h" to show help (common in other CLI tools), even though in sqlcmd "-h" is
// traditionally used to set header count. When "-h" has a following number, it's
// left as-is to maintain compatibility with the traditional behavior.
func preprocessHelpFlags(args []string) []string {
result := make([]string, 0, len(args))
for i := 0; i < len(args); i++ {
arg := args[i]

// Convert "-help" to "--help"
if arg == "-help" {
result = append(result, "--help")
continue
}

// Handle "-h" without an argument: convert to "-?" to show help
if arg == "-h" {
// Check if next arg exists
hasNextArg := i+1 < len(args)
nextIsNumber := false
nextIsFlag := false
if hasNextArg {
nextArg := args[i+1]
// Check if next arg looks like a number (including negative numbers)
// This must be checked before checking if it's a flag, because
// negative numbers start with '-'
_, err := strconv.Atoi(nextArg)
nextIsNumber = err == nil

// Check if next arg is a flag (starts with '-' but is not a number)
if !nextIsNumber && len(nextArg) > 0 && nextArg[0] == '-' {
nextIsFlag = true
}
}

// If -h is followed by a number, keep it as-is (header count)
// Otherwise, convert to -? (show help) and consume any non-numeric, non-flag argument
if !nextIsNumber {
result = append(result, "-?")
// If there was a following non-numeric, non-flag argument, consume it
// (Don't consume flags, as they should be processed normally)
if hasNextArg && !nextIsFlag {
i++
}
} else {
result = append(result, arg)
}
continue
}

result = append(result, arg)
}
return result
}

// If args[i] is the given flag and args[i+1] is another flag, returns the value to append after the flag
func checkDefaultValue(args []string, i int) (val string) {
flags := map[rune]string{
Expand Down
39 changes: 39 additions & 0 deletions cmd/sqlcmd/sqlcmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ func TestValidCommandLineToArgsConversion(t *testing.T) {
{[]string{"-h", "2", "-?"}, func(args SQLCmdArguments) bool {
return args.Help && args.Headers == 2
}},
{[]string{"-h"}, func(args SQLCmdArguments) bool {
return args.Help
}},
{[]string{"-help"}, func(args SQLCmdArguments) bool {
return args.Help
}},
{[]string{"-h", "abc"}, func(args SQLCmdArguments) bool {
return args.Help
}},
{[]string{"-u", "-A"}, func(args SQLCmdArguments) bool {
return args.UnicodeOutputFile && args.DedicatedAdminConnection
}},
Expand Down Expand Up @@ -584,6 +593,36 @@ func TestConvertOsArgs(t *testing.T) {
[]string{"-X", "-k2"},
[]string{"-X", "0", "-k2"},
},
{
"-h without argument converts to -? for help",
[]string{"-h"},
[]string{"-?"},
},
{
"-h with number keeps -h for headers",
[]string{"-h", "5"},
[]string{"-h", "5"},
},
{
"-h with negative number keeps -h for headers",
[]string{"-h", "-1"},
[]string{"-h", "-1"},
},
{
"-help converts to --help",
[]string{"-help"},
[]string{"--help"},
},
{
"-h followed by flag converts to -? for help",
[]string{"-h", "-E"},
[]string{"-?", "-E"},
},
{
"-h followed by non-numeric converts to -? for help (consuming the non-numeric arg)",
[]string{"-h", "abc"},
[]string{"-?"},
},
}
for _, c := range tests {
t.Run(c.name, func(t *testing.T) {
Expand Down
Loading