Skip to content
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ The following switches have different behavior in this version of `sqlcmd` compa
- To provide the value of the host name in the server certificate when using strict encryption, pass the host name with `-F`. Example: `-Ns -F myhost.domain.com`
- More information about client/server encryption negotiation can be found at <https://docs.microsoft.com/openspecs/windows_protocols/ms-tds/60f56408-0188-4cd5-8b90-25c6f2423868>
- `-u` The generated Unicode output file will have the UTF16 Little-Endian Byte-order mark (BOM) written to it.
- `-f` Specifies the code page for input and output files. Format: `codepage | i:codepage[,o:codepage] | o:codepage[,i:codepage]`. Use `65001` for UTF-8. Supported codepages include Unicode (65001, 1200, 1201), Windows (874, 1250-1258), OEM/DOS (437, 850, etc.), ISO-8859 (28591-28606), CJK (932, 936, 949, 950), and EBCDIC (37, 1047, 1140). Use `--list-codepages` to see all supported code pages.
- Some behaviors that were kept to maintain compatibility with `OSQL` may be changed, such as alignment of column headers for some data types.
- All commands must fit on one line, even `EXIT`. Interactive mode will not check for open parentheses or quotes for commands and prompt for successive lines. The ODBC sqlcmd allows the query run by `EXIT(query)` to span multiple lines.
- `-i` doesn't handle a comma `,` in a file name correctly unless the file name argument is triple quoted. For example:
Expand Down
28 changes: 28 additions & 0 deletions 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
CodePage string
ListCodePages bool
// Keep Help at the end of the list
Help bool
}
Expand Down Expand Up @@ -171,6 +173,10 @@ func (a *SQLCmdArguments) Validate(c *cobra.Command) (err error) {
err = rangeParameterError("-t", fmt.Sprint(a.QueryTimeout), 0, 65534, true)
case a.ServerCertificate != "" && !encryptConnectionAllowsTLS(a.EncryptConnection):
err = localizer.Errorf("The -J parameter requires encryption to be enabled (-N true, -N mandatory, or -N strict).")
case a.CodePage != "":
if _, parseErr := sqlcmd.ParseCodePage(a.CodePage); parseErr != nil {
err = localizer.Errorf(`'-f %s': %v`, a.CodePage, parseErr)
}
}
}
if err != nil {
Expand Down Expand Up @@ -239,6 +245,17 @@ func Execute(version string) {
listLocalServers()
os.Exit(0)
}
// List supported codepages
if args.ListCodePages {
fmt.Println(localizer.Sprintf("Supported Code Pages:"))
fmt.Println()
fmt.Printf("%-8s %-20s %s\n", "Code", "Name", "Description")
fmt.Printf("%-8s %-20s %s\n", "----", "----", "-----------")
for _, cp := range sqlcmd.SupportedCodePages() {
fmt.Printf("%-8d %-20s %s\n", cp.CodePage, cp.Name, cp.Description)
}
os.Exit(0)
}
if len(argss) > 0 {
fmt.Printf("%s'%s': Unknown command. Enter '--help' for command help.", sqlcmdErrorPrefix, argss[0])
os.Exit(1)
Expand Down Expand Up @@ -479,6 +496,8 @@ func setFlags(rootCmd *cobra.Command, args *SQLCmdArguments) {
rootCmd.Flags().BoolVarP(&args.EnableColumnEncryption, "enable-column-encryption", "g", false, localizer.Sprintf("Enable column encryption"))
rootCmd.Flags().StringVarP(&args.ChangePassword, "change-password", "z", "", localizer.Sprintf("New password"))
rootCmd.Flags().StringVarP(&args.ChangePasswordAndExit, "change-password-exit", "Z", "", localizer.Sprintf("New password and exit"))
rootCmd.Flags().StringVarP(&args.CodePage, "code-page", "f", "", localizer.Sprintf("Specifies the code page for input/output. Use 65001 for UTF-8. Format: codepage | i:codepage[,o:codepage] | o:codepage[,i:codepage]"))
rootCmd.Flags().BoolVar(&args.ListCodePages, "list-codepages", false, localizer.Sprintf("List supported code pages and exit"))
}

func setScriptVariable(v string) string {
Expand Down Expand Up @@ -813,6 +832,15 @@ func run(vars *sqlcmd.Variables, args *SQLCmdArguments) (int, error) {
defer s.StopCloseHandler()
s.UnicodeOutputFile = args.UnicodeOutputFile

// Parse and apply codepage settings
if args.CodePage != "" {
codePageSettings, err := sqlcmd.ParseCodePage(args.CodePage)
if err != nil {
return 1, localizer.Errorf("Invalid code page: %v", err)
}
s.CodePage = codePageSettings
}

if args.DisableCmd != nil {
s.Cmd.DisableSysCommands(args.errorOnBlockedCmd())
}
Comment thread
dlevy-msft-sql marked this conversation as resolved.
Expand Down
21 changes: 21 additions & 0 deletions cmd/sqlcmd/sqlcmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,22 @@ 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"
}},
// Codepage flag tests
{[]string{"-f", "65001"}, func(args SQLCmdArguments) bool {
return args.CodePage == "65001"
}},
{[]string{"-f", "i:1252,o:65001"}, func(args SQLCmdArguments) bool {
return args.CodePage == "i:1252,o:65001"
}},
{[]string{"-f", "o:65001,i:1252"}, func(args SQLCmdArguments) bool {
return args.CodePage == "o:65001,i:1252"
}},
{[]string{"--code-page", "1252"}, func(args SQLCmdArguments) bool {
return args.CodePage == "1252"
}},
{[]string{"--list-codepages"}, func(args SQLCmdArguments) bool {
return args.ListCodePages
}},
}

for _, test := range commands {
Expand Down Expand Up @@ -178,6 +194,11 @@ func TestInvalidCommandLine(t *testing.T) {
{[]string{"-N", "optional", "-J", "/path/to/cert.pem"}, "The -J parameter requires encryption to be enabled (-N true, -N mandatory, or -N strict)."},
{[]string{"-N", "disable", "-J", "/path/to/cert.pem"}, "The -J parameter requires encryption to be enabled (-N true, -N mandatory, or -N strict)."},
{[]string{"-N", "strict", "-F", "myserver.domain.com", "-J", "/path/to/cert.pem"}, "The -F and the -J options are mutually exclusive."},
// Codepage validation tests
{[]string{"-f", "invalid"}, `'-f invalid': invalid codepage: invalid`},
{[]string{"-f", "99999"}, `'-f 99999': unsupported codepage 99999`},
{[]string{"-f", "i:invalid"}, `'-f i:invalid': invalid input codepage: i:invalid`},
{[]string{"-f", "x:1252"}, `'-f x:1252': invalid codepage: x:1252`},
}

for _, test := range commands {
Expand Down
Loading
Loading