Skip to content

Commit 76685e9

Browse files
authored
Initial version of go-sqlcmd (#1)
* commandline and variables * add displayname to build tasks * implement quit command * initial version of batch parsing * move variables * new package for variables * add vscode helpers for debug * fix go and quit processing * implement Out and Error commands * fix custom batch separator * move connectionString to sqlcmd * Add sql connection and print column headers * add row and error processing * remove unused package * fix binary rendering and screen fitting * rewrite decodeBinary for performance * fix test pipeline * remove password command line param * exit on ctrl-c * implement -q and -Q * de-lint and update readme * add lint for PRs * separate pkg and cmd folders * fix pipeline for new folder layout * de-lint and reduce public surface * more de-linting * hopefully last round of de-linting
1 parent 3d4fc05 commit 76685e9

28 files changed

+4180
-10
lines changed

.github/workflows/golangci-lint.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: golangci-lint
2+
on:
3+
push:
4+
branches:
5+
- main
6+
pull_request:
7+
jobs:
8+
golangci-pr:
9+
name: lint-pr-changes
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v2
13+
- name: golangci-lint
14+
uses: golangci/golangci-lint-action@v2
15+
with:
16+
version: v1.42.0
17+
only-new-issues: true

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,8 @@
1313

1414
# Dependency directories (remove the comment below to include it)
1515
# vendor/
16+
17+
coverage.json
18+
coverage.txt
19+
coverage.xml
20+
testresults.xml

.pipelines/TestSql2017.yml

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
pool:
2+
vmImage: 'ubuntu-latest'
3+
4+
steps:
5+
- task: GoTool@0
6+
inputs:
7+
version: '1.16.5'
8+
- task: Go@0
9+
displayName: 'Go: get dependencies'
10+
inputs:
11+
command: 'get'
12+
arguments: '-d'
13+
workingDirectory: '$(Build.SourcesDirectory)/cmd/sqlcmd'
14+
15+
16+
- task: Go@0
17+
displayName: 'Go: install gotest.tools/gotestsum'
18+
inputs:
19+
command: 'custom'
20+
customCommand: 'install'
21+
arguments: 'gotest.tools/gotestsum@latest'
22+
workingDirectory: '$(System.DefaultWorkingDirectory)'
23+
24+
- task: Go@0
25+
displayName: 'Go: install github.com/axw/gocov/gocov'
26+
inputs:
27+
command: 'custom'
28+
customCommand: 'install'
29+
arguments: 'github.com/axw/gocov/gocov@latest'
30+
workingDirectory: '$(System.DefaultWorkingDirectory)'
31+
32+
- task: Go@0
33+
displayName: 'Go: install github.com/axw/gocov/gocov'
34+
inputs:
35+
command: 'custom'
36+
customCommand: 'install'
37+
arguments: 'github.com/AlekSi/gocov-xml@latest'
38+
workingDirectory: '$(System.DefaultWorkingDirectory)'
39+
40+
#Your build pipeline references an undefined variables named SQLPASSWORD.
41+
#Create or edit the build pipeline for this YAML file, define the variable on the Variables tab. See https://go.microsoft.com/fwlink/?linkid=865972
42+
43+
- task: Docker@2
44+
displayName: 'Run SQL 2017 docker image'
45+
inputs:
46+
command: run
47+
arguments: '-m 2GB -e ACCEPT_EULA=1 -d --name sql2017 -p:1433:1433 -e SA_PASSWORD=$(SQLPASSWORD) mcr.microsoft.com/mssql/server:2017-latest'
48+
49+
- script: |
50+
~/go/bin/gotestsum --junitfile testresults.xml -- ./... -coverprofile=coverage.txt -covermode count
51+
~/go/bin/gocov convert coverage.txt > coverage.json
52+
~/go/bin/gocov-xml < coverage.json > coverage.xml
53+
mkdir coverage
54+
workingDirectory: '$(Build.SourcesDirectory)'
55+
displayName: 'run tests'
56+
env:
57+
SQLPASSWORD: $(SQLPASSWORD)
58+
SQLCMDUSER: sa
59+
SQLCMDPASSWORD: $(SQLPASSWORD)
60+
continueOnError: true
61+
- task: PublishTestResults@2
62+
displayName: "Publish junit-style results"
63+
inputs:
64+
testResultsFiles: 'testresults.xml'
65+
testResultsFormat: JUnit
66+
searchFolder: '$(Build.SourcesDirectory)'
67+
testRunTitle: 'SQL 2017 - $(Build.SourceBranchName)'
68+
condition: always()
69+
continueOnError: true
70+
71+
- task: PublishCodeCoverageResults@1
72+
inputs:
73+
codeCoverageTool: Cobertura
74+
pathToSources: '$(Build.SourcesDirectory)'
75+
summaryFileLocation: $(Build.SourcesDirectory)/**/coverage.xml
76+
reportDirectory: $(Build.SourcesDirectory)/**/coverage
77+
failIfCoverageEmpty: true
78+
condition: always()
79+
continueOnError: true
80+

.vscode/launch.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "Attach using delve",
9+
"type": "go",
10+
"request": "attach",
11+
"preLaunchTask": "delve",
12+
"mode": "remote",
13+
"remotePath": "${workspaceFolder}",
14+
"port" : 23456,
15+
"host" : "127.0.0.1",
16+
"cwd" : "${workspaceFolder}",
17+
},
18+
{
19+
"name" : "Run query and exit",
20+
"type" : "go",
21+
"request": "launch",
22+
"mode" : "auto",
23+
"program": "${fileDirname}",
24+
"args" : ["-Q", "\"select 100 as Count\""],
25+
}
26+
]
27+
}

.vscode/tasks.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
// See https://go.microsoft.com/fwlink/?LinkId=733558
3+
// for the documentation about the tasks.json format
4+
"version": "2.0.0",
5+
"tasks": [
6+
{
7+
"label": "delve",
8+
"type": "shell",
9+
"command": "dlv debug --headless --listen=:23456 --api-version=2 \"${workspaceFolder}\"",
10+
"isBackground": true,
11+
"presentation": {
12+
"focus": true,
13+
"panel": "dedicated",
14+
"clear": false
15+
},
16+
"group": {
17+
"kind": "build",
18+
"isDefault": true
19+
},
20+
"problemMatcher": {
21+
"pattern": {
22+
"regexp": ""
23+
},
24+
"background": {
25+
"activeOnStart": true,
26+
"beginsPattern": {
27+
"regexp": ".*"
28+
},
29+
"endsPattern": {
30+
"regexp": ".*server listening.*"
31+
}
32+
}
33+
}
34+
}
35+
]
36+
}

README.md

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,29 @@
1-
# Project
1+
# SQL Utilities - Go edition
22

3-
> This repo has been populated by an initial template to help get you started. Please
4-
> make sure to update the content to build a great experience for community-building.
3+
This repo contains command line tools and go packages for working with Microsoft SQL Server, Azure SQL Database, and Azure Synapse.
54

6-
As the maintainer of this project, please make a few updates:
5+
## Sqlcmd
76

8-
- Improving this README.MD file to provide a great experience
9-
- Updating SUPPORT.MD with content about this project's support experience
10-
- Understanding the security reporting process in SECURITY.MD
11-
- Remove this section from the README
7+
The `sqlcmd` project aims to be a complete port of the native sqlcmd to the `go` language, utilizing the [go-mssqldb](https://github.com/denisenkom/go-mssqldb) driver. For full documentation of the tool, see https://docs.microsoft.com/sql/tools/sqlcmd-utility
8+
9+
### Breaking changes
10+
11+
We will be implementing as many command line switches and behaviors as possible over time. Several switches and behaviors are expected to change in this implementation.
12+
13+
- `-P` switch will be removed. Passwords for SQL authentication can only be provided through these mechanisms:
14+
15+
-The `SQLCMDPASSWORD` environment variable
16+
-The `:CONNECT` command
17+
-When prompted, the user can type the password to complete a connection
18+
19+
- `-R` switch will be removed. The go runtime does not provide access to user locale information, and it's not readily available through syscall on all supported platforms.
20+
- Some behaviors that were kept to maintain compatibility with `OSQL` may be changed, such as alignment of column headers for some data types.
21+
22+
### Packages
23+
24+
#### sqlcmd
25+
26+
#### batch
1227

1328
## Contributing
1429

@@ -26,8 +41,9 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio
2641

2742
## Trademarks
2843

29-
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
30-
trademarks or logos is subject to and must follow
44+
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
45+
trademarks or logos is subject to and must follow
3146
[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
3247
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
3348
Any use of third-party trademarks or logos are subject to those third-party's policies.
49+

cmd/sqlcmd/main.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
package main
5+
6+
import (
7+
"fmt"
8+
"os"
9+
10+
"github.com/alecthomas/kong"
11+
"github.com/microsoft/go-sqlcmd/pkg/sqlcmd"
12+
"github.com/xo/usql/rline"
13+
)
14+
15+
// SQLCmdArguments defines the command line arguments for sqlcmd
16+
// The exhaustive list is at https://docs.microsoft.com/sql/tools/sqlcmd-utility?view=sql-server-ver15
17+
type SQLCmdArguments struct {
18+
// Which batch terminator to use. Default is GO
19+
BatchTerminator string `short:"c" default:"GO" arghelp:"Specifies the batch terminator. The default value is GO."`
20+
// Whether to trust the server certificate on an encrypted connection
21+
TrustServerCertificate bool `short:"C" help:"Implicitly trust the server certificate without validation."`
22+
DatabaseName string `short:"d" help:"This option sets the sqlcmd scripting variable SQLCMDDBNAME. This parameter specifies the initial database. The default is your login's default-database property. If the database does not exist, an error message is generated and sqlcmd exits."`
23+
UseTrustedConnection bool `short:"E" xor:"uid" help:"Uses a trusted connection instead of using a user name and password to sign in to SQL Server, ignoring any any environment variables that define user name and password."`
24+
UserName string `short:"U" xor:"uid" help:"The login name or contained database user name. For contained database users, you must provide the database name option"`
25+
// Files from which to read query text
26+
InputFile []string `short:"i" xor:"input1, input2" type:"existingFile" help:"Identifies one or more files that contain batches of SQL statements. If one or more files do not exist, sqlcmd will exit. Mutually exclusive with -Q/-q."`
27+
OutputFile string `short:"o" type:"path" help:"Identifies the file that receives output from sqlcmd."`
28+
// First query to run in interactive mode
29+
InitialQuery string `short:"q" xor:"input1" help:"Executes a query when sqlcmd starts, but does not exit sqlcmd when the query has finished running. Multiple-semicolon-delimited queries can be executed."`
30+
// Query to run then exit
31+
Query string `short:"Q" xor:"input2" help:"Executes a query when sqlcmd starts and then immediately exits sqlcmd. Multiple-semicolon-delimited queries can be executed."`
32+
Server string `short:"S" help:"[tcp:]server[\\instance_name][,port]Specifies the instance of SQL Server to which to connect. It sets the sqlcmd scripting variable SQLCMDSERVER."`
33+
// Disable syscommands with a warning
34+
DisableCmdAndWarn bool `short:"X" xor:"syscmd" help:"Disables commands that might compromise system security. Sqlcmd issues a warning and continues."`
35+
}
36+
37+
// Breaking changes in command line are listed here.
38+
// Any switch not listed in breaking changes and not also included in SqlCmdArguments just has not been implemented yet
39+
// 1. -P: Passwords have to be provided through SQLCMDPASSWORD environment variable or typed when prompted
40+
// 2. -R: Go runtime doesn't expose user locale information and syscall would only enable it on Windows, so we won't try to implement it
41+
42+
var args SQLCmdArguments
43+
44+
func main() {
45+
kong.Parse(&args)
46+
vars := sqlcmd.InitializeVariables(!args.DisableCmdAndWarn)
47+
setVars(vars, &args)
48+
49+
exitCode, err := run(vars)
50+
if err != nil {
51+
fmt.Println(err.Error())
52+
}
53+
os.Exit(exitCode)
54+
}
55+
56+
// Initializes scripting variables from command line arguments
57+
func setVars(vars *sqlcmd.Variables, args *SQLCmdArguments) {
58+
varmap := map[string]func(*SQLCmdArguments) string{
59+
sqlcmd.SQLCMDDBNAME: func(a *SQLCmdArguments) string { return a.DatabaseName },
60+
sqlcmd.SQLCMDLOGINTIMEOUT: func(a *SQLCmdArguments) string { return "" },
61+
sqlcmd.SQLCMDUSEAAD: func(a *SQLCmdArguments) string { return "" },
62+
sqlcmd.SQLCMDWORKSTATION: func(a *SQLCmdArguments) string { return "" },
63+
sqlcmd.SQLCMDSERVER: func(a *SQLCmdArguments) string { return a.Server },
64+
sqlcmd.SQLCMDERRORLEVEL: func(a *SQLCmdArguments) string { return "" },
65+
sqlcmd.SQLCMDPACKETSIZE: func(a *SQLCmdArguments) string { return "" },
66+
sqlcmd.SQLCMDUSER: func(a *SQLCmdArguments) string { return a.UserName },
67+
sqlcmd.SQLCMDSTATTIMEOUT: func(a *SQLCmdArguments) string { return "" },
68+
sqlcmd.SQLCMDHEADERS: func(a *SQLCmdArguments) string { return "" },
69+
sqlcmd.SQLCMDCOLSEP: func(a *SQLCmdArguments) string { return "" },
70+
sqlcmd.SQLCMDCOLWIDTH: func(a *SQLCmdArguments) string { return "" },
71+
sqlcmd.SQLCMDMAXVARTYPEWIDTH: func(a *SQLCmdArguments) string { return "" },
72+
sqlcmd.SQLCMDMAXFIXEDTYPEWIDTH: func(a *SQLCmdArguments) string { return "" },
73+
}
74+
for varname, set := range varmap {
75+
val := set(args)
76+
if val != "" {
77+
vars.Set(varname, val)
78+
}
79+
}
80+
}
81+
82+
func run(vars *sqlcmd.Variables) (exitcode int, err error) {
83+
wd, err := os.Getwd()
84+
if err != nil {
85+
return 1, err
86+
}
87+
if args.BatchTerminator != "GO" {
88+
err = sqlcmd.SetBatchTerminator(args.BatchTerminator)
89+
if err != nil {
90+
err = fmt.Errorf("invalid batch terminator '%s'", args.BatchTerminator)
91+
}
92+
}
93+
if err != nil {
94+
return 1, err
95+
}
96+
97+
iactive := args.InputFile == nil
98+
line, err := rline.New(!iactive, "", "")
99+
if err != nil {
100+
return 1, err
101+
}
102+
defer line.Close()
103+
104+
s := sqlcmd.New(line, wd, vars)
105+
s.Connect.UseTrustedConnection = args.UseTrustedConnection
106+
s.Connect.TrustServerCertificate = args.TrustServerCertificate
107+
s.Format = sqlcmd.NewSQLCmdDefaultFormatter(false)
108+
if args.OutputFile != "" {
109+
err = s.RunCommand(sqlcmd.Commands["OUT"], []string{args.OutputFile})
110+
if err != nil {
111+
return 1, err
112+
}
113+
}
114+
once := false
115+
if args.InitialQuery != "" {
116+
s.Query = args.InitialQuery
117+
} else if args.Query != "" {
118+
once = true
119+
s.Query = args.Query
120+
}
121+
err = s.ConnectDb("", "", "", !iactive)
122+
if err != nil {
123+
return 1, err
124+
}
125+
if iactive {
126+
err = s.Run(once)
127+
}
128+
return s.Exitcode, err
129+
}

0 commit comments

Comments
 (0)