Skip to content

Commit 123f28d

Browse files
authored
implement -u switch (#79)
* implement -u switch * fix test for linux
1 parent 75c2815 commit 123f28d

File tree

12 files changed

+61
-8
lines changed

12 files changed

+61
-8
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Binaries for programs and plugins
22
output
3-
.exe
3+
*.exe
44
*.exe~
55
*.dll
66
*.so

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ We will be implementing command line switches and behaviors over time. Several s
2323
- If `-N` is provided but `-C` is not, sqlcmd will require validation of the server certificate. Note that a `false` value for encryption could still lead to encryption of the login packet.
2424
- If both `-N` and `-C` are provided, sqlcmd will use their values for encryption negotiation.
2525
- More information about client/server encryption negotiation can be found at <https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/60f56408-0188-4cd5-8b90-25c6f2423868>
26+
- `-u` The generated Unicode output file will have the UTF16 Little-Endian Byte-order mark (BOM) written to it.
2627
- Some behaviors that were kept to maintain compatibility with `OSQL` may be changed, such as alignment of column headers for some data types.
2728
- 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.
2829

cmd/sqlcmd/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,10 @@ type SQLCmdArguments struct {
4949
ErrorLevel int `short:"m" help:"Controls which error messages are sent to stdout. Messages that have severity level greater than or equal to this level are sent."`
5050
Format string `short:"F" help:"Specifies the formatting for results." default:"horiz" enum:"horiz,horizontal,vert,vertical"`
5151
ErrorsToStderr int `short:"r" help:"Redirects the error message output to the screen (stderr). A value of 0 means messages with severity >= 11 will b redirected. A value of 1 means all error message output including PRINT is redirected." enum:"-1,0,1" default:"-1"`
52-
Help bool `short:"?" help:"Show syntax summary."`
5352
Headers int `short:"h" help:"Specifies the number of rows to print between the column headings. Use -h-1 to specify that headers not be printed."`
53+
UnicodeOutputFile bool `short:"u" help:"Specifies that all output files are encoded with little-endian Unicode"`
54+
// Keep Help at the end of the list
55+
Help bool `short:"?" help:"Show syntax summary."`
5456
}
5557

5658
// Validate accounts for settings not described by Kong attributes
@@ -212,6 +214,7 @@ func run(vars *sqlcmd.Variables, args *SQLCmdArguments) (int, error) {
212214
}
213215

214216
s := sqlcmd.New(line, wd, vars)
217+
s.UnicodeOutputFile = args.UnicodeOutputFile
215218
setConnect(&s.Connect, args, vars)
216219
if args.BatchTerminator != "GO" {
217220
err = s.Cmd.SetBatchTerminator(args.BatchTerminator)

cmd/sqlcmd/main_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package main
44

55
import (
66
"os"
7+
"runtime"
78
"strings"
89
"testing"
910

@@ -80,6 +81,9 @@ func TestValidCommandLineToArgsConversion(t *testing.T) {
8081
{[]string{"-h", "2", "-?"}, func(args SQLCmdArguments) bool {
8182
return args.Help && args.Headers == 2
8283
}},
84+
{[]string{"-u"}, func(args SQLCmdArguments) bool {
85+
return args.UnicodeOutputFile
86+
}},
8387
}
8488

8589
for _, test := range commands {
@@ -144,6 +148,37 @@ func TestRunInputFiles(t *testing.T) {
144148
}
145149
}
146150

151+
func TestUnicodeOutput(t *testing.T) {
152+
o, err := os.CreateTemp("", "sqlcmdmain")
153+
assert.NoError(t, err, "os.CreateTemp")
154+
defer os.Remove(o.Name())
155+
defer o.Close()
156+
args = newArguments()
157+
args.InputFile = []string{"testdata/selectutf8.txt"}
158+
args.OutputFile = o.Name()
159+
args.UnicodeOutputFile = true
160+
if canTestAzureAuth() {
161+
args.UseAad = true
162+
}
163+
vars := sqlcmd.InitializeVariables(!args.DisableCmdAndWarn)
164+
setVars(vars, &args)
165+
166+
exitCode, err := run(vars, &args)
167+
assert.NoError(t, err, "run")
168+
assert.Equal(t, 0, exitCode, "exitCode")
169+
bytes, err := os.ReadFile(o.Name())
170+
if assert.NoError(t, err, "os.ReadFile") {
171+
outfile := `testdata/unicodeout_linux.txt`
172+
if runtime.GOOS == "windows" {
173+
outfile = `testdata/unicodeout.txt`
174+
}
175+
expectedBytes, err := os.ReadFile(outfile)
176+
if assert.NoErrorf(t, err, "Unable to open %s", outfile) {
177+
assert.Equalf(t, expectedBytes, bytes, "unicode output bytes should match %s", outfile)
178+
}
179+
}
180+
}
181+
147182
func TestQueryAndExit(t *testing.T) {
148183
o, err := os.CreateTemp("", "sqlcmdmain")
149184
assert.NoError(t, err, "os.CreateTemp")

cmd/sqlcmd/testdata/selectutf8.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
select N'挨挨唉哀皑癌蔼矮' as SimplifiedChinese
2+

cmd/sqlcmd/testdata/unicodeout.txt

156 Bytes
Binary file not shown.
146 Bytes
Binary file not shown.

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/google/uuid v1.3.0
1010
github.com/peterh/liner v1.2.2
1111
github.com/stretchr/testify v1.7.1
12+
golang.org/x/text v0.3.6
1213
)
1314

1415
replace github.com/denisenkom/go-mssqldb => github.com/shueybubbles/go-mssqldb v0.10.1-0.20220317022252-fafb9d92e469

go.sum

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0 h1:OYa9vmRX2XC5GXRAzegg
44
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
55
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0 h1:v9p9TfTbf7AwNb5NYQt7hI41IfPoLFiFkLtb+bmGjT0=
66
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
7-
github.com/alecthomas/kong v0.2.18-0.20210621093454-54558f65e86f h1:VgRM6/wqZIB1D9W3XMllm/wplTmPgI5yvCHUXEsmKps=
8-
github.com/alecthomas/kong v0.2.18-0.20210621093454-54558f65e86f/go.mod h1:ka3VZ8GZNPXv9Ov+j4YNLkI8mTuhXyr/0ktSlqIydQQ=
97
github.com/alecthomas/kong v0.5.0 h1:u8Kdw+eeml93qtMZ04iei0CFYve/WPcA5IFh+9wSskE=
108
github.com/alecthomas/kong v0.5.0/go.mod h1:uzxf/HUh0tj43x1AyJROl3JT7SgsZ5m+icOv1csRhc0=
9+
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48=
1110
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
1211
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1312
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -32,9 +31,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
3231
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
3332
github.com/shueybubbles/go-mssqldb v0.10.1-0.20220317022252-fafb9d92e469 h1:BuUMqsxB86i1QEBf0q+dkQYfNLVpD1nH1fRJPKvXWSg=
3433
github.com/shueybubbles/go-mssqldb v0.10.1-0.20220317022252-fafb9d92e469/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU=
35-
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
3634
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
37-
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
3835
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
3936
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
4037
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=

pkg/sqlcmd/commands.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"strings"
1212

1313
"github.com/alecthomas/kong"
14+
"golang.org/x/text/encoding/unicode"
15+
"golang.org/x/text/transform"
1416
)
1517

1618
// Command defines a sqlcmd action which can be intermixed with the SQL batch
@@ -200,7 +202,15 @@ func outCommand(s *Sqlcmd, args []string, line uint) error {
200202
if err != nil {
201203
return InvalidFileError(err, args[0])
202204
}
203-
s.SetOutput(o)
205+
if s.UnicodeOutputFile {
206+
// ODBC sqlcmd doesn't write a BOM but we will.
207+
// Maybe the endian-ness should be configurable.
208+
win16le := unicode.UTF16(unicode.LittleEndian, unicode.UseBOM)
209+
encoder := transform.NewWriter(o, win16le.NewEncoder())
210+
s.SetOutput(encoder)
211+
} else {
212+
s.SetOutput(o)
213+
}
204214
}
205215
return nil
206216
}

0 commit comments

Comments
 (0)