├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── golangci-lint.yml │ └── pr-validation.yml ├── .gitignore ├── .pipelines ├── TestSql2017.yml ├── include-install-go-tools.yml └── include-runtests-linux.yml ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── LICENSE ├── NOTICE.md ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── build ├── NOTICE.header ├── NOTICE.tpl ├── arch.txt ├── build.cmd └── build.sh ├── cmd ├── modern │ ├── doc.go │ ├── main.go │ ├── main_test.go │ ├── options.go │ ├── root.go │ ├── root │ │ ├── config.go │ │ ├── config │ │ │ ├── add-context.go │ │ │ ├── add-context_test.go │ │ │ ├── add-endpoint.go │ │ │ ├── add-endpoint_test.go │ │ │ ├── add-user.go │ │ │ ├── add-user_test.go │ │ │ ├── connection-strings.go │ │ │ ├── connection-strings_test.go │ │ │ ├── current-context.go │ │ │ ├── current-context_test.go │ │ │ ├── delete-context.go │ │ │ ├── delete-context_test.go │ │ │ ├── delete-endpoint.go │ │ │ ├── delete-endpoint_test.go │ │ │ ├── delete-user.go │ │ │ ├── delete-user_test.go │ │ │ ├── get-contexts.go │ │ │ ├── get-contexts_test.go │ │ │ ├── get-endpoints.go │ │ │ ├── get-endpoints_test.go │ │ │ ├── get-users.go │ │ │ ├── get-users_test.go │ │ │ ├── use-context.go │ │ │ ├── use-context_test.go │ │ │ ├── view.go │ │ │ └── view_test.go │ │ ├── config_test.go │ │ ├── install.go │ │ ├── install │ │ │ ├── edge.go │ │ │ ├── edge │ │ │ │ ├── get-tags.go │ │ │ │ └── get-tags_test.go │ │ │ ├── edge_test.go │ │ │ ├── mssql-base.go │ │ │ ├── mssql-base_test.go │ │ │ ├── mssql.go │ │ │ ├── mssql │ │ │ │ ├── get-tags.go │ │ │ │ └── get-tags_test.go │ │ │ └── mssql_test.go │ │ ├── install_test.go │ │ ├── open.go │ │ ├── open │ │ │ ├── ads.go │ │ │ ├── ads_darwin.go │ │ │ ├── ads_linux.go │ │ │ ├── ads_test.go │ │ │ ├── ads_windows.go │ │ │ └── ads_windows_test.go │ │ ├── open_test.go │ │ ├── query.go │ │ ├── query_test.go │ │ ├── start.go │ │ ├── start_test.go │ │ ├── stop.go │ │ ├── stop_test.go │ │ ├── uninstall.go │ │ └── uninstall_test.go │ ├── root_test.go │ ├── sqlconfig │ │ ├── doc.go │ │ └── sqlconfig.go │ └── winres │ │ └── winres.json ├── sqlcmd-linter │ └── main.go └── sqlcmd │ ├── pipe_detection_test.go │ ├── sqlcmd.go │ ├── sqlcmd_test.go │ ├── stdin_console_test.go │ └── testdata │ ├── bad.sql │ ├── create100db.sql │ ├── drop100db.txt │ ├── select,100.sql │ ├── select100.sql │ ├── selectunicode_BE.txt │ ├── selectunicode_LE.txt │ ├── selectutf8.txt │ ├── selectutf8_bom.txt │ ├── unicodeout.txt │ ├── unicodeout_linux.txt │ ├── utf8out.txt │ └── utf8out_linux.txt ├── go.mod ├── go.sum ├── internal ├── buffer │ ├── memory-buffer.go │ └── memory-buffer_test.go ├── cmdparser │ ├── cmd.go │ ├── cmd_test.go │ ├── dependency │ │ └── options.go │ ├── factory.go │ ├── factory_test.go │ ├── interface.go │ ├── options.go │ ├── test.go │ ├── test_test.go │ └── type.go ├── color │ ├── color.go │ └── color_test.go ├── config │ ├── config.go │ ├── config_test.go │ ├── context.go │ ├── endpoint-container.go │ ├── endpoint-container_test.go │ ├── endpoint.go │ ├── endpoint_test.go │ ├── error.go │ ├── error_test.go │ ├── initialize.go │ ├── trace.go │ ├── user.go │ ├── user_test.go │ ├── viper.go │ └── viper_test.go ├── container │ ├── controller.go │ ├── controller_test.go │ ├── docker.go │ ├── error.go │ ├── error_test.go │ ├── initialize.go │ └── trace.go ├── credman │ ├── credman_linux.go │ ├── credman_windows.go │ ├── credman_windows_test.go │ └── types_windows.go ├── doc.go ├── http │ ├── error.go │ ├── error_test.go │ ├── http.go │ ├── http_test.go │ ├── initialize.go │ └── trace.go ├── intialize.go ├── intialize_test.go ├── io │ ├── file │ │ ├── error.go │ │ ├── error_test.go │ │ ├── file.go │ │ ├── file_test.go │ │ ├── initialize.go │ │ └── trace.go │ └── folder │ │ ├── error.go │ │ ├── error_test.go │ │ ├── folder.go │ │ ├── folder_test.go │ │ ├── initialize.go │ │ └── trace.go ├── localizer │ ├── constants.go │ ├── localizer.go │ └── localizer_test.go ├── net │ ├── error.go │ ├── error_test.go │ ├── initialize.go │ ├── net.go │ ├── net_test.go │ └── trace.go ├── output │ ├── factory.go │ ├── factory_test.go │ ├── formatter │ │ ├── base.go │ │ ├── base_test.go │ │ ├── factory.go │ │ ├── factory_test.go │ │ ├── interface.go │ │ ├── json.go │ │ ├── options.go │ │ ├── xml.go │ │ └── yaml.go │ ├── options.go │ ├── output.go │ ├── output_test.go │ ├── type.go │ └── verbosity │ │ └── level.go ├── pal │ ├── error.go │ ├── intialize.go │ ├── pal.go │ ├── pal_darwin.go │ ├── pal_linux.go │ ├── pal_test.go │ └── pal_windows.go ├── secret │ ├── encryption_darwin.go │ ├── encryption_linux.go │ ├── encryption_windows.go │ ├── error.go │ ├── error_test.go │ ├── generate.go │ ├── generate_test.go │ ├── initialize.go │ ├── secret.go │ └── secret_test.go ├── sql │ ├── error.go │ ├── error_test.go │ ├── factory.go │ ├── initialize.go │ ├── interface.go │ ├── mock.go │ ├── mock_test.go │ ├── mssql.go │ ├── mssql_test.go │ ├── trace.go │ └── types.go ├── test │ ├── executor.go │ └── executor_test.go ├── tools │ ├── factory.go │ ├── factory_test.go │ ├── tool │ │ ├── ads.go │ │ ├── ads_darwin.go │ │ ├── ads_linux.go │ │ ├── ads_test.go │ │ ├── ads_windows.go │ │ ├── interface.go │ │ ├── tool.go │ │ ├── tool_darwin.go │ │ ├── tool_linux.go │ │ ├── tool_test.go │ │ ├── tool_windows.go │ │ └── types.go │ └── tools.go └── translations │ ├── LCL │ ├── de-DE │ │ └── out.gotext.json.lcl │ ├── es-ES │ │ └── out.gotext.json.lcl │ ├── fr-FR │ │ └── out.gotext.json.lcl │ ├── it-IT │ │ └── out.gotext.json.lcl │ ├── ja-JP │ │ └── out.gotext.json.lcl │ ├── ko-KR │ │ └── out.gotext.json.lcl │ ├── pt-BR │ │ └── out.gotext.json.lcl │ ├── ru-RU │ │ └── out.gotext.json.lcl │ ├── zh-CN │ │ └── out.gotext.json.lcl │ └── zh-TW │ │ └── out.gotext.json.lcl │ ├── LocProject.json │ ├── P306PairNamesToProcess.lss │ ├── catalog.go │ ├── catalog_test.go │ ├── locales │ ├── de-DE │ │ ├── messages.gotext.json │ │ └── out.gotext.json │ ├── en-US │ │ └── out.gotext.json │ ├── es-ES │ │ ├── messages.gotext.json │ │ └── out.gotext.json │ ├── fr-FR │ │ ├── messages.gotext.json │ │ └── out.gotext.json │ ├── it-IT │ │ ├── messages.gotext.json │ │ └── out.gotext.json │ ├── ja-JP │ │ ├── messages.gotext.json │ │ └── out.gotext.json │ ├── ko-KR │ │ ├── messages.gotext.json │ │ └── out.gotext.json │ ├── pt-BR │ │ ├── messages.gotext.json │ │ └── out.gotext.json │ ├── ru-RU │ │ ├── messages.gotext.json │ │ └── out.gotext.json │ ├── zh-CN │ │ ├── messages.gotext.json │ │ └── out.gotext.json │ └── zh-TW │ │ ├── messages.gotext.json │ │ └── out.gotext.json │ ├── localized │ ├── de-DE │ │ └── out.gotext.json │ ├── es-ES │ │ └── out.gotext.json │ ├── fr-FR │ │ └── out.gotext.json │ ├── it-IT │ │ └── out.gotext.json │ ├── ja-JP │ │ └── out.gotext.json │ ├── ko-KR │ │ └── out.gotext.json │ ├── pt-BR │ │ └── out.gotext.json │ ├── ru-RU │ │ └── out.gotext.json │ ├── zh-CN │ │ └── out.gotext.json │ └── zh-TW │ │ └── out.gotext.json │ └── translations.go ├── pkg ├── console │ ├── complete.go │ ├── complete_test.go │ ├── console.go │ ├── console_redirect.go │ └── console_redirect_test.go ├── sqlcmd-linter │ ├── imports.go │ ├── imports_test.go │ ├── testdata │ │ └── src │ │ │ ├── github.com │ │ │ ├── README.md │ │ │ ├── alecthomas │ │ │ │ └── chroma │ │ │ │ │ └── chroma.go │ │ │ ├── microsoft │ │ │ │ └── go-sqlcmd │ │ │ │ │ └── pkg │ │ │ │ │ └── sqlcmd │ │ │ │ │ └── sqlcmd.go │ │ │ └── stretchr │ │ │ │ └── testify │ │ │ │ └── assert │ │ │ │ └── main.go │ │ │ ├── imports_linter_tests │ │ │ ├── cmd │ │ │ │ └── main │ │ │ │ │ └── main.go │ │ │ └── internal │ │ │ │ └── main.go │ │ │ └── useassert_linter_tests │ │ │ └── assert.go │ ├── useasserts.go │ └── useasserts_test.go └── sqlcmd │ ├── azure_auth.go │ ├── batch.go │ ├── batch_test.go │ ├── commands.go │ ├── commands_test.go │ ├── connect.go │ ├── errors.go │ ├── exec_darwin.go │ ├── exec_linux.go │ ├── exec_windows.go │ ├── format.go │ ├── format_darwin.go │ ├── format_linux.go │ ├── format_test.go │ ├── format_windows.go │ ├── parse.go │ ├── parse_test.go │ ├── sqlcmd.go │ ├── sqlcmd_test.go │ ├── sqlcmd_windows_amd64.go │ ├── testdata │ ├── blanks.sql │ ├── quotedidentifiers.sql │ ├── selectdates.sql │ ├── singlebatchnogo.sql │ ├── testerrorredirection.sql │ ├── twobatchnoendinggo.sql │ ├── twobatchwithgo.sql │ └── variablesnogo.sql │ ├── util.go │ ├── variables.go │ └── variables_test.go ├── release ├── linux │ ├── deb │ │ ├── README.md │ │ ├── build-pkg.sh │ │ ├── pipeline-test.sh │ │ ├── pipeline.sh │ │ └── prepare-rules.sh │ ├── docker │ │ ├── README.md │ │ ├── pipeline-test.sh │ │ └── pipeline.sh │ └── rpm │ │ ├── README.md │ │ ├── build-rpm.sh │ │ ├── pipeline-test.sh │ │ ├── pipeline.sh │ │ └── sqlcmd.spec └── windows │ ├── choco │ ├── sqlcmd.nuspec │ └── tools │ │ ├── LICENSE.txt │ │ ├── VERIFICATION.txt │ │ └── chocolateyinstall.ps1 │ └── msi │ ├── README.md │ ├── eula │ └── eulatext_1033.rtf │ ├── product.wxs │ ├── resources │ ├── CLI_LICENSE.rtf │ ├── banner.bmp │ ├── dialog.bmp │ ├── go-sqlcmd_256.png │ └── sqlcmd.ico │ ├── scripts │ ├── pipeline-test.ps1 │ └── pipeline.cmd │ ├── sqlcmd.sln │ └── sqlcmd.wixproj └── testdata └── sql.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*.go] 5 | indent_style = tab 6 | # (Please don't specify an indent_size here; that has too many unintended consequences.) 7 | 8 | # IDE0073: File header 9 | file_header_template = Copyright (c) Microsoft Corporation.\nLicensed under the MIT license. 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 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/setup-go@v5 13 | with: 14 | go-version: stable 15 | - uses: actions/checkout@v4 16 | - name: golangci-lint 17 | uses: golangci/golangci-lint-action@v6 18 | with: 19 | version: latest 20 | only-new-issues: true 21 | -------------------------------------------------------------------------------- /.github/workflows/pr-validation.yml: -------------------------------------------------------------------------------- 1 | name: pr-validation 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Setup go 14 | uses: actions/setup-go@v2 15 | with: 16 | go-version: '1.22' 17 | - name: Run tests against Linux SQL 18 | run: | 19 | go version 20 | cd cmd/sqlcmd 21 | go get -d 22 | go build . 23 | export SQLCMDPASSWORD=$(date +%s|sha256sum|base64|head -c 32) 24 | export SQLCMDUSER=sa 25 | docker run -m 2GB -e ACCEPT_EULA=1 -d --name sql2022 -p:1433:1433 -e SA_PASSWORD=$SQLCMDPASSWORD mcr.microsoft.com/mssql/server:2022-latest 26 | cd ../.. 27 | go test -v ./... 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin 8 | obj 9 | 10 | # Test binary, built with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Dependency directories (remove the comment below to include it) 17 | # vendor/ 18 | 19 | coverage.json 20 | coverage.txt 21 | coverage.xml 22 | testresults.xml 23 | 24 | # .syso is generated by go-winres. Only needed for official builds 25 | *.syso 26 | 27 | # IDE files 28 | .idea 29 | 30 | darwin-amd64/sqlcmd 31 | darwin-arm64/sqlcmd 32 | linux-amd64/sqlcmd 33 | linux-arm64/sqlcmd 34 | linux-s390x/sqlcmd 35 | -------------------------------------------------------------------------------- /.pipelines/include-install-go-tools.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: GoTool@0 3 | inputs: 4 | version: '1.22.10' 5 | - task: Go@0 6 | displayName: 'Go: get dependencies' 7 | inputs: 8 | command: 'get' 9 | arguments: '-d' 10 | workingDirectory: '$(Build.SourcesDirectory)/cmd/sqlcmd' 11 | 12 | - task: Go@0 13 | displayName: 'Go: install gotest.tools/gotestsum' 14 | inputs: 15 | command: 'custom' 16 | customCommand: 'install' 17 | arguments: 'gotest.tools/gotestsum@latest' 18 | workingDirectory: '$(System.DefaultWorkingDirectory)' 19 | 20 | - task: Go@0 21 | displayName: 'Go: install github.com/axw/gocov/gocov' 22 | inputs: 23 | command: 'custom' 24 | customCommand: 'install' 25 | arguments: 'github.com/axw/gocov/gocov@latest' 26 | workingDirectory: '$(System.DefaultWorkingDirectory)' 27 | 28 | - task: Go@0 29 | displayName: 'Go: install github.com/axw/gocov/gocov' 30 | inputs: 31 | command: 'custom' 32 | customCommand: 'install' 33 | arguments: 'github.com/AlekSi/gocov-xml@latest' 34 | workingDirectory: '$(System.DefaultWorkingDirectory)' 35 | -------------------------------------------------------------------------------- /.pipelines/include-runtests-linux.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: RunName 3 | type: string 4 | - name: SQLCMDUSER 5 | type: string 6 | default: '' 7 | - name: SQLPASSWORD 8 | type: string 9 | default: '' 10 | - name: AZURECLIENTSECRET 11 | type: string 12 | default: '' 13 | - name: SQLCMDSERVER 14 | type: string 15 | default: . 16 | - name: SQLCMDDBNAME 17 | type: string 18 | default: '' 19 | steps: 20 | - script: | 21 | ~/go/bin/gotestsum --junitfile "${{ parameters.RunName }}.testresults.xml" -- ./... -coverprofile="${{ parameters.RunName }}.coverage.txt" -covermode count 22 | ~/go/bin/gocov convert "${{ parameters.RunName }}.coverage.txt" > "${{ parameters.RunName }}.coverage.json" 23 | ~/go/bin/gocov-xml < "${{ parameters.RunName }}.coverage.json" > ${{ parameters.RunName }}.coverage.xml 24 | mkdir -p coverage 25 | workingDirectory: '$(Build.SourcesDirectory)' 26 | displayName: 'run tests' 27 | env: 28 | SQLPASSWORD: ${{ parameters.SQLPASSWORD }} 29 | SQLCMDUSER: ${{ parameters.SQLCMDUSER }} 30 | SQLCMDPASSWORD: ${{ parameters.SQLPASSWORD }} 31 | AZURE_TENANT_ID: $(AZURE_TENANT_ID) 32 | AZURE_CLIENT_ID: $(AZURE_CLIENT_ID) 33 | AZURE_CLIENT_SECRET: ${{ parameters.AZURECLIENTSECRET }} 34 | SQLCMDSERVER: ${{ parameters.SQLCMDSERVER }} 35 | SQLCMDDBNAME: ${{ parameters.SQLCMDDBNAME }} 36 | continueOnError: true 37 | 38 | - task: PublishTestResults@2 39 | displayName: "Publish junit-style results" 40 | inputs: 41 | testResultsFiles: '${{ parameters.RunName }}.testresults.xml' 42 | testResultsFormat: JUnit 43 | searchFolder: '$(Build.SourcesDirectory)' 44 | testRunTitle: '${{ parameters.RunName }} - $(Build.SourceBranchName)' 45 | failTaskOnFailedTests: true 46 | condition: always() 47 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 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 | { 9 | 10 | "name": "Attach to Process", 11 | 12 | "type": "go", 13 | 14 | "request": "attach", 15 | 16 | "mode": "local", 17 | 18 | "processId": 0 19 | 20 | }, 21 | { 22 | "name" : "Run query and exit", 23 | "type" : "go", 24 | "request": "launch", 25 | "mode" : "auto", 26 | "program": "${workspaceFolder}/cmd/modern", 27 | "args" : ["-S", "davidshi-2022", "-g", "-Q", "select nvarcharcol from encrypt.dbo.encrypted", "--driver-logging-level=65"], 28 | }, 29 | { 30 | "name" : "Run file query", 31 | "type" : "go", 32 | "request": "launch", 33 | "mode" : "auto", 34 | "program": "${workspaceFolder}/cmd/modern", 35 | "args" : ["-S", "np:.", "-i", "${workspaceFolder}/cmd/sqlcmd/testdata/select100.sql"], 36 | }, 37 | { 38 | "name" : "Run sqlcmdlinter", 39 | "type" : "go", 40 | "request" : "launch", 41 | "mode" : "auto", 42 | "program": "${workspaceFolder}/cmd/sqlcmd-linter", 43 | "args" : ["${workspaceFolder}/..."] 44 | 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.lintTool": "golangci-lint", 3 | "go.lintOnSave": "workspace", 4 | "azure-pipelines.1ESPipelineTemplatesSchemaFile": true 5 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 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 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT license. 4 | #------------------------------------------------------------------------------ 5 | 6 | # Example: 7 | # docker run --rm microsoft/sqlcmd sqlcmd --help 8 | # 9 | 10 | FROM scratch 11 | ARG BUILD_DATE 12 | ARG PACKAGE_VERSION 13 | 14 | LABEL maintainer="Microsoft" \ 15 | org.label-schema.schema-version="1.0" \ 16 | org.label-schema.vendor="Microsoft" \ 17 | org.label-schema.name="SQLCMD CLI" \ 18 | org.label-schema.version=$PACKAGE_VERSION \ 19 | org.label-schema.license="https://github.com/microsoft/go-sqlcmd/blob/main/LICENSE" \ 20 | org.label-schema.description="The MSSQL SQLCMD CLI tool" \ 21 | org.label-schema.url="https://github.com/microsoft/go-sqlcmd" \ 22 | org.label-schema.usage="https://docs.microsoft.com/sql/tools/sqlcmd-utility" \ 23 | org.label-schema.build-date=$BUILD_DATE \ 24 | org.label-schema.docker.cmd="docker run -it microsoft/sqlcmd:$PACKAGE_VERSION" 25 | 26 | COPY ./sqlcmd /usr/bin/sqlcmd 27 | 28 | CMD ["sqlcmd"] 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License (MIT) 2 | 3 | Copyright © Microsoft Corp. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # TODO: The maintainer of this repo has not yet edited this file 2 | 3 | **REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? 4 | 5 | - **No CSS support:** Fill out this template with information about how to file issues and get help. 6 | - **Yes CSS support:** Fill out an intake form at [aka.ms/spot](https://aka.ms/spot). CSS will work with/help you to determine next steps. More details also available at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). 7 | - **Not sure?** Fill out a SPOT intake as though the answer were "Yes". CSS will help you decide. 8 | 9 | *Then remove this first heading from this SUPPORT.MD file before publishing your repo.* 10 | 11 | # Support 12 | 13 | ## How to file issues and get help 14 | 15 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 16 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 17 | feature request as a new Issue. 18 | 19 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE 20 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER 21 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**. 22 | 23 | ## Microsoft Support Policy 24 | 25 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. 26 | -------------------------------------------------------------------------------- /build/NOTICE.header: -------------------------------------------------------------------------------- 1 | # NOTICES 2 | 3 | This repository incorporates material as listed below or described in the code. 4 | -------------------------------------------------------------------------------- /build/NOTICE.tpl: -------------------------------------------------------------------------------- 1 | 2 | {{ range . }} 3 | ## {{ .Name }} 4 | 5 | * Name: {{ .Name }} 6 | * Version: {{ .Version }} 7 | * License: [{{ .LicenseName }}]({{ .LicenseURL }}) 8 | 9 | ``` 10 | {{ .LicenseText }} 11 | ``` 12 | {{ end }} 13 | -------------------------------------------------------------------------------- /build/arch.txt: -------------------------------------------------------------------------------- 1 | darwin,amd64,sqlcmd 2 | darwin,arm64,sqlcmd 3 | linux,amd64,sqlcmd 4 | linux,arm64,sqlcmd 5 | linux,s390x,sqlcmd 6 | windows,arm64,sqlcmd.exe 7 | windows,amd64,sqlcmd.exe 8 | windows,arm,sqlcmd.exe -------------------------------------------------------------------------------- /build/build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM We get the value of the escape character by using PROMPT $E 4 | for /F "tokens=1,2 delims=#" %%a in ('"prompt #$H#$E# & echo on & for %%b in (1) do rem"') do ( 5 | set "DEL=%%a" 6 | set "ESC=%%b" 7 | ) 8 | 9 | REM Get Version Tag 10 | for /f %%i in ('"git describe --tags --abbrev=0"') do set sqlcmdVersion=%%i 11 | 12 | setlocal 13 | SET RED=%ESC%[1;31m 14 | @echo %RED% 15 | REM run the custom sqlcmd linter for code style enforcement 16 | REM using for/do instead of running it directly so the status code isn't checked by the shell. 17 | REM Once we are prepared to block the build with the linter we will move this step into a pipeline 18 | @for /F "usebackq" %%l in (`go run cmd\sqlcmd-linter\main.go -test %~dp0../...`) DO echo %%l 19 | @echo %ESC%[0m 20 | 21 | if not exist %gopath%\bin\go-winres.exe ( 22 | go install github.com/tc-hib/go-winres@latest 23 | ) 24 | if not exist %gopath%\bin\gotext.exe ( 25 | go install golang.org/x/text/cmd/gotext@latest 26 | ) 27 | 28 | REM go-winres likes to append instead of overwrite so delete existing resource file 29 | del %~dp0..\cmd\modern\*.syso 30 | 31 | REM generates translations file and resources 32 | go generate %~dp0../... 2> %~dp0generate.txt 33 | if NOT ERRORLEVEL 0 ( 34 | type %~dp0generate.txt 35 | goto :end 36 | ) else ( 37 | echo Fix any conflicting localizable strings: 38 | @echo %RED% 39 | @findstr conflicting "%~dp0generate.txt" 40 | @echo %ESC%[0m 41 | ) 42 | endlocal 43 | 44 | REM Generates sqlcmd.exe in the root dir of the repo 45 | go build -o %~dp0..\sqlcmd.exe -ldflags="-X main.version=%sqlcmdVersion%" %~dp0..\cmd\modern 46 | 47 | REM Generate NOTICE 48 | if not exist %gopath%\bin\go-licenses.exe ( 49 | go install github.com/google/go-licenses@latest 50 | ) 51 | go-licenses report github.com/microsoft/go-sqlcmd/cmd/modern --template build\NOTICE.tpl --ignore github.com/microsoft > %~dp0notice.txt 2>nul 52 | copy %~dp0NOTICE.header + %~dp0notice.txt %~dp0..\NOTICE.md 53 | del %~dp0notice.txt 54 | 55 | REM Generates all versions of sqlcmd in platform-specific folder 56 | setlocal 57 | 58 | for /F "tokens=1-3 delims=," %%i in (%~dp0arch.txt) do set GOOS=%%i&set GOARCH=%%j&go build -o %~dp0..\%%i-%%j\%%k -ldflags="-X main.version=%sqlcmdVersion%" %~dp0..\cmd\modern 59 | endlocal 60 | 61 | :end 62 | del %~dp0generate.txt 63 | -------------------------------------------------------------------------------- /build/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | scriptdir=`dirname $0` 3 | versionTag=`git describe --tags --abbrev=0` 4 | go build -o $scriptdir/../sqlcmd -ldflags="-X main.version=$versionTag" $scriptdir/../cmd/modern 5 | 6 | go install github.com/google/go-licenses@latest 7 | go-licenses report github.com/microsoft/go-sqlcmd/cmd/modern --template build/NOTICE.tpl --ignore github.com/microsoft > $scriptDir/notice.txt 8 | cat $scriptDir/NOTICE.header $scriptDir/notice.txt > $scriptDir/../NOTICE.md 9 | rm $scriptDir/notice.txt 10 | -------------------------------------------------------------------------------- /cmd/modern/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package main 5 | 6 | /* 7 | Main package (main.go) is the entry point for the sqlcmd CLI application. 8 | 9 | To follow the flow of this code: 10 | 11 | 1. enter through main.go, (TEMPORARY: decision made whether to invoke the modern 12 | cobra CLI 13 | 2. Then cmd/cmd.go, see the init() func `New` the `Root` cmd (and all its 14 | subcommands) 15 | 3. The command-line is then parsed and internal.Initialize() runs (with 16 | the logging level, config file path, error handling and trace support passed 17 | into internal packages) 18 | 4. Now go to the cmd/root/… folder structure, and read the DefineCommand 19 | function for the command (sqlcmd install, sqlcmd query etc.) being run 20 | 5. Each cmd/root/... command has a `run` method that performs the action 21 | 6. All the commands (cmd/root/…) use the /internal packages to abstract from error 22 | handling and trace (non-localized) logging (as can be seen from the `import` 23 | for each command (in /cmd/root/...)). 24 | 25 | This code follows the Go Style Guide 26 | 27 | - https://google.github.io/styleguide/go/ 28 | - https://go.dev/doc/effective_go 29 | - https://github.com/golang-standards/project-layout 30 | 31 | Exceptions to Go Style Guide: 32 | 33 | - None 34 | */ 35 | -------------------------------------------------------------------------------- /cmd/modern/main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package main 5 | 6 | import ( 7 | "errors" 8 | "github.com/microsoft/go-sqlcmd/internal/buffer" 9 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 10 | "github.com/microsoft/go-sqlcmd/internal/cmdparser/dependency" 11 | "github.com/microsoft/go-sqlcmd/internal/output" 12 | "github.com/microsoft/go-sqlcmd/internal/pal" 13 | "github.com/stretchr/testify/assert" 14 | "os" 15 | "testing" 16 | ) 17 | 18 | func TestMainStart(t *testing.T) { 19 | os.Args[1] = "--help" 20 | main() 21 | } 22 | 23 | func TestInitializeCallback(t *testing.T) { 24 | rootCmd = cmdparser.New[*Root](dependency.Options{}) 25 | initializeCallback() 26 | } 27 | 28 | func TestDisplayHints(t *testing.T) { 29 | buf := buffer.NewMemoryBuffer() 30 | outputter = output.New(output.Options{StandardWriter: buf}) 31 | displayHints([]string{"This is a hint"}) 32 | assert.Equal(t, pal.LineBreak()+ 33 | "HINT:"+ 34 | pal.LineBreak()+ 35 | " 1. This is a hint"+pal.LineBreak()+pal.LineBreak(), buf.String()) 36 | err := buf.Close() 37 | checkErr(err) 38 | } 39 | 40 | func TestCheckErr(t *testing.T) { 41 | rootCmd = cmdparser.New[*Root](dependency.Options{}) 42 | rootCmd.loggingLevel = 4 43 | checkErr(nil) 44 | assert.Panics(t, func() { 45 | checkErr(errors.New("test error")) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /cmd/modern/options.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package main 5 | 6 | type GlobalOptions struct { 7 | TrustServerCertificate bool 8 | DatabaseName string 9 | UseTrustedConnection bool 10 | UserName string 11 | Endpoint string 12 | AuthenticationMethod string 13 | UseAad bool 14 | PacketSize int 15 | LoginTimeout int 16 | WorkstationName string 17 | ApplicationIntent string 18 | Encrypt string 19 | DriverLogLevel int 20 | } 21 | -------------------------------------------------------------------------------- /cmd/modern/root/config/add-context_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func TestAddContext(t *testing.T) { 13 | cmdparser.TestSetup(t) 14 | cmdparser.TestCmd[*AddEndpoint]() 15 | cmdparser.TestCmd[*AddContext]("--endpoint endpoint") 16 | } 17 | 18 | func TestNegAddContext(t *testing.T) { 19 | cmdparser.TestSetup(t) 20 | assert.Panics(t, func() { 21 | cmdparser.TestCmd[*AddContext]("--endpoint does-not-exist") 22 | }) 23 | } 24 | 25 | func TestNegAddContext2(t *testing.T) { 26 | cmdparser.TestSetup(t) 27 | cmdparser.TestCmd[*AddEndpoint]() 28 | assert.Panics(t, func() { 29 | cmdparser.TestCmd[*AddContext]("--endpoint endpoint --user does-not-exist") 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /cmd/modern/root/config/add-endpoint_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "testing" 9 | ) 10 | 11 | func TestAddEndpoint(t *testing.T) { 12 | cmdparser.TestSetup(t) 13 | cmdparser.TestCmd[*AddEndpoint]() 14 | } 15 | -------------------------------------------------------------------------------- /cmd/modern/root/config/connection-strings_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal" 8 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 9 | "github.com/microsoft/go-sqlcmd/internal/output" 10 | "github.com/stretchr/testify/assert" 11 | "os" 12 | "testing" 13 | ) 14 | 15 | // TestConnectionStrings tests that the `sqlcmd config connection-strings` command 16 | // works as expected 17 | func TestConnectionStrings(t *testing.T) { 18 | cmdparser.TestSetup(t) 19 | 20 | output := output.New(output.Options{HintHandler: func(hints []string) {}, ErrorHandler: func(err error) {}}) 21 | options := internal.InitializeOptions{ 22 | ErrorHandler: func(err error) { 23 | if err != nil { 24 | panic(err) 25 | } 26 | }, 27 | HintHandler: func(strings []string) {}, 28 | TraceHandler: output.Tracef, 29 | LineBreak: "\n", 30 | } 31 | internal.Initialize(options) 32 | 33 | if os.Getenv("SQLCMDPASSWORD") == "" && 34 | os.Getenv("SQLCMD_PASSWORD") == "" { 35 | os.Setenv("SQLCMDPASSWORD", "it's-a-secret") 36 | defer os.Setenv("SQLCMDPASSWORD", "") 37 | } 38 | 39 | cmdparser.TestCmd[*AddEndpoint]() 40 | cmdparser.TestCmd[*AddUser]("--username user --password-encryption none") 41 | cmdparser.TestCmd[*AddContext]("--endpoint endpoint --user user") 42 | cmdparser.TestCmd[*ConnectionStrings]() 43 | 44 | // Add endpoint with no user 45 | cmdparser.TestCmd[*AddContext]("--endpoint endpoint") 46 | cmdparser.TestCmd[*ConnectionStrings]() 47 | 48 | // Add endpoint to Azure SQL (connection strings won't Trust server cert) 49 | cmdparser.TestCmd[*AddEndpoint]("--address server.database.windows.net") 50 | cmdparser.TestCmd[*AddUser]("--username user --password-encryption none") 51 | cmdparser.TestCmd[*AddContext]("--endpoint endpoint2 --user user") 52 | 53 | result := cmdparser.TestCmd[*ConnectionStrings]() 54 | assert.Contains(t, result, "database=master") 55 | 56 | result = cmdparser.TestCmd[*ConnectionStrings]("--database tempdb") 57 | assert.NotContains(t, result, "database=master") 58 | assert.Contains(t, result, "database=tempdb") 59 | } 60 | -------------------------------------------------------------------------------- /cmd/modern/root/config/current-context.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/microsoft/go-sqlcmd/internal/config" 9 | "github.com/microsoft/go-sqlcmd/internal/localizer" 10 | ) 11 | 12 | // CurrentContext implements the `sqlcmd config current-context` command 13 | type CurrentContext struct { 14 | cmdparser.Cmd 15 | } 16 | 17 | func (c *CurrentContext) DefineCommand(...cmdparser.CommandOptions) { 18 | options := cmdparser.CommandOptions{ 19 | Use: "current-context", 20 | Short: localizer.Sprintf("Display the current-context"), 21 | Examples: []cmdparser.ExampleOptions{ 22 | { 23 | Description: localizer.Sprintf("Display the current-context"), 24 | Steps: []string{ 25 | "sqlcmd config current-context"}, 26 | }, 27 | }, 28 | Run: c.run} 29 | 30 | c.Cmd.DefineCommand(options) 31 | } 32 | 33 | func (c *CurrentContext) run() { 34 | output := c.Output() 35 | output.Infof("%v\n", config.CurrentContextName()) 36 | } 37 | -------------------------------------------------------------------------------- /cmd/modern/root/config/current-context_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "testing" 9 | ) 10 | 11 | func TestCurrentContext(t *testing.T) { 12 | cmdparser.TestSetup(t) 13 | cmdparser.TestCmd[*CurrentContext]() 14 | } 15 | -------------------------------------------------------------------------------- /cmd/modern/root/config/delete-context_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func TestDeleteContext(t *testing.T) { 13 | cmdparser.TestSetup(t) 14 | cmdparser.TestCmd[*AddUser]("--name delete-test --username user --auth-type other") 15 | cmdparser.TestCmd[*AddEndpoint]() 16 | cmdparser.TestCmd[*AddContext]("--endpoint endpoint --user delete-test") 17 | cmdparser.TestCmd[*DeleteContext]("--name context") 18 | } 19 | 20 | func TestNegDeleteContext(t *testing.T) { 21 | cmdparser.TestSetup(t) 22 | assert.Panics(t, func() { 23 | cmdparser.TestCmd[*DeleteContext]() 24 | }) 25 | } 26 | 27 | func TestNegDeleteContext2(t *testing.T) { 28 | cmdparser.TestSetup(t) 29 | assert.Panics(t, func() { 30 | cmdparser.TestCmd[*DeleteContext]("--name does-not-exist") 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /cmd/modern/root/config/delete-endpoint.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/microsoft/go-sqlcmd/internal/config" 9 | "github.com/microsoft/go-sqlcmd/internal/localizer" 10 | ) 11 | 12 | // DeleteEndpoint implements the `sqlcmd config delete-endpoint` command 13 | type DeleteEndpoint struct { 14 | cmdparser.Cmd 15 | 16 | name string 17 | } 18 | 19 | func (c *DeleteEndpoint) DefineCommand(...cmdparser.CommandOptions) { 20 | options := cmdparser.CommandOptions{ 21 | Use: "delete-endpoint", 22 | Short: localizer.Sprintf("Delete an endpoint"), 23 | Examples: []cmdparser.ExampleOptions{ 24 | { 25 | Description: localizer.Sprintf("Delete an endpoint"), 26 | Steps: []string{ 27 | "sqlcmd config delete-endpoint --name my-endpoint", 28 | "sqlcmd config delete-context endpoint"}, 29 | }, 30 | }, 31 | Run: c.run, 32 | 33 | FirstArgAlternativeForFlag: &cmdparser.AlternativeForFlagOptions{Flag: "name", Value: &c.name}, 34 | } 35 | 36 | c.Cmd.DefineCommand(options) 37 | 38 | c.AddFlag(cmdparser.FlagOptions{ 39 | String: &c.name, 40 | Name: "name", 41 | Usage: localizer.Sprintf("Name of endpoint to delete")}) 42 | } 43 | 44 | // run is used to delete an endpoint with the given name. If the specified endpoint 45 | // does not exist, the function will print an error message and return. If the 46 | // endpoint exists, it will be deleted and a success message will be printed. 47 | func (c *DeleteEndpoint) run() { 48 | output := c.Output() 49 | 50 | if c.name == "" { 51 | output.Fatal(localizer.Sprintf("Endpoint name must be provided. Provide endpoint name with %s flag", localizer.NameFlag)) 52 | } 53 | 54 | if config.EndpointExists(c.name) { 55 | config.DeleteEndpoint(c.name) 56 | } else { 57 | output.FatalWithHintExamples([][]string{ 58 | {localizer.Sprintf("View endpoints"), "sqlcmd config get-endpoints"}, 59 | }, 60 | localizer.Sprintf("Endpoint '%v' does not exist", c.name)) 61 | } 62 | 63 | output.Info(localizer.Sprintf("Endpoint '%v' deleted", c.name)) 64 | } 65 | -------------------------------------------------------------------------------- /cmd/modern/root/config/delete-endpoint_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func TestDeleteEndpoint(t *testing.T) { 13 | cmdparser.TestSetup(t) 14 | cmdparser.TestCmd[*AddEndpoint]() 15 | cmdparser.TestCmd[*DeleteEndpoint]("--name endpoint") 16 | } 17 | 18 | func TestNegDeleteEndpoint(t *testing.T) { 19 | cmdparser.TestSetup(t) 20 | assert.Panics(t, func() { 21 | cmdparser.TestCmd[*DeleteEndpoint]() 22 | }) 23 | } 24 | 25 | func TestNegDeleteEndpoint2(t *testing.T) { 26 | cmdparser.TestSetup(t) 27 | assert.Panics(t, func() { 28 | cmdparser.TestCmd[*DeleteEndpoint]("--name does-not-exist") 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /cmd/modern/root/config/delete-user.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/microsoft/go-sqlcmd/internal/config" 9 | "github.com/microsoft/go-sqlcmd/internal/localizer" 10 | ) 11 | 12 | // DeleteUser implements the `sqlcmd config delete-user` command 13 | type DeleteUser struct { 14 | cmdparser.Cmd 15 | 16 | name string 17 | } 18 | 19 | func (c *DeleteUser) DefineCommand(...cmdparser.CommandOptions) { 20 | options := cmdparser.CommandOptions{ 21 | Use: "delete-user", 22 | Short: localizer.Sprintf("Delete a user"), 23 | Examples: []cmdparser.ExampleOptions{ 24 | { 25 | Description: localizer.Sprintf("Delete a user"), 26 | Steps: []string{ 27 | "sqlcmd config delete-user --name user1", 28 | "sqlcmd config delete-user user1"}}, 29 | }, 30 | Run: c.run, 31 | 32 | FirstArgAlternativeForFlag: &cmdparser.AlternativeForFlagOptions{ 33 | Flag: "name", Value: &c.name}, 34 | } 35 | 36 | c.Cmd.DefineCommand(options) 37 | 38 | c.AddFlag(cmdparser.FlagOptions{ 39 | String: &c.name, 40 | Name: "name", 41 | Usage: localizer.Sprintf("Name of user to delete")}) 42 | } 43 | 44 | func (c *DeleteUser) run() { 45 | output := c.Output() 46 | 47 | if c.name == "" { 48 | output.Fatal(localizer.Sprintf("User name must be provided. Provide user name with %s flag", localizer.NameFlag)) 49 | } 50 | 51 | if config.UserNameExists(c.name) { 52 | config.DeleteUser(c.name) 53 | } else { 54 | output.FatalWithHintExamples([][]string{ 55 | {localizer.Sprintf("View users"), "sqlcmd config get-users"}, 56 | }, 57 | localizer.Sprintf("User %q does not exist", c.name)) 58 | } 59 | 60 | output.Info(localizer.Sprintf("User %q deleted", c.name)) 61 | } 62 | -------------------------------------------------------------------------------- /cmd/modern/root/config/delete-user_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/stretchr/testify/assert" 9 | "os" 10 | "testing" 11 | ) 12 | 13 | func TestDeleteUser(t *testing.T) { 14 | cmdparser.TestSetup(t) 15 | 16 | // SQLCMDPASSWORD is already set in the build pipelines, so don't 17 | // overwrite it here. 18 | if os.Getenv("SQLCMDPASSWORD") == "" && os.Getenv("SQLCMD_PASSWORD") == "" { 19 | os.Setenv("SQLCMDPASSWORD", "it's-a-secret") 20 | defer os.Setenv("SQLCMDPASSWORD", "") 21 | } 22 | cmdparser.TestSetup(t) 23 | cmdparser.TestCmd[*AddUser]("--username user1 --password-encryption none") 24 | cmdparser.TestCmd[*DeleteUser]("--name user") 25 | } 26 | 27 | func TestNegDeleteUserNoName(t *testing.T) { 28 | cmdparser.TestSetup(t) 29 | 30 | assert.Panics(t, func() { 31 | cmdparser.TestCmd[*DeleteUser]() 32 | }) 33 | } 34 | 35 | func TestNegDeleteUserInvalidName(t *testing.T) { 36 | cmdparser.TestSetup(t) 37 | 38 | assert.Panics(t, func() { 39 | cmdparser.TestCmd[*DeleteUser]("--name bad-bad") 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /cmd/modern/root/config/get-contexts.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/microsoft/go-sqlcmd/internal/config" 9 | "github.com/microsoft/go-sqlcmd/internal/localizer" 10 | ) 11 | 12 | // GetContexts implements the `sqlcmd config get-contexts` command 13 | type GetContexts struct { 14 | cmdparser.Cmd 15 | 16 | name string 17 | detailed bool 18 | } 19 | 20 | func (c *GetContexts) DefineCommand(...cmdparser.CommandOptions) { 21 | options := cmdparser.CommandOptions{ 22 | Use: "get-contexts", 23 | Short: localizer.Sprintf("Display one or many contexts from the sqlconfig file"), 24 | Examples: []cmdparser.ExampleOptions{ 25 | { 26 | Description: localizer.Sprintf("List all the context names in your sqlconfig file"), 27 | Steps: []string{"sqlcmd config get-contexts"}, 28 | }, 29 | { 30 | Description: localizer.Sprintf("List all the contexts in your sqlconfig file"), 31 | Steps: []string{"sqlcmd config get-contexts --detailed"}, 32 | }, 33 | { 34 | Description: localizer.Sprintf("Describe one context in your sqlconfig file"), 35 | Steps: []string{"sqlcmd config get-contexts my-context"}, 36 | }, 37 | }, 38 | Run: c.run, 39 | 40 | FirstArgAlternativeForFlag: &cmdparser.AlternativeForFlagOptions{Flag: "name", Value: &c.name}, 41 | } 42 | 43 | c.Cmd.DefineCommand(options) 44 | 45 | c.AddFlag(cmdparser.FlagOptions{ 46 | String: &c.name, 47 | Name: "name", 48 | Usage: localizer.Sprintf("Context name to view details of")}) 49 | 50 | c.AddFlag(cmdparser.FlagOptions{ 51 | Bool: &c.detailed, 52 | Name: "detailed", 53 | Usage: localizer.Sprintf("Include context details")}) 54 | } 55 | 56 | func (c *GetContexts) run() { 57 | output := c.Output() 58 | 59 | if c.name != "" { 60 | if config.ContextExists(c.name) { 61 | context := config.GetContext(c.name) 62 | output.Struct(context) 63 | } else { 64 | output.FatalWithHints( 65 | []string{localizer.Sprintf("To view available contexts run `%s`", localizer.GetContextCommand)}, 66 | localizer.Sprintf("error: no context exists with the name: \"%v\"", c.name)) 67 | } 68 | } else { 69 | config.OutputContexts(output.Struct, c.detailed) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /cmd/modern/root/config/get-contexts_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func TestGetContexts(t *testing.T) { 13 | cmdparser.TestSetup(t) 14 | cmdparser.TestCmd[*AddEndpoint]("--name endpoint") 15 | cmdparser.TestCmd[*AddContext]("--endpoint endpoint") 16 | cmdparser.TestCmd[*GetContexts]() 17 | cmdparser.TestCmd[*GetContexts]("context") 18 | } 19 | 20 | func TestNegGetContexts(t *testing.T) { 21 | cmdparser.TestSetup(t) 22 | assert.Panics(t, func() { 23 | cmdparser.TestCmd[*GetContexts]("does-not-exist") 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /cmd/modern/root/config/get-endpoints.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/microsoft/go-sqlcmd/internal/config" 9 | "github.com/microsoft/go-sqlcmd/internal/localizer" 10 | ) 11 | 12 | // GetEndpoints implements the `sqlcmd config get-endpoints` command 13 | type GetEndpoints struct { 14 | cmdparser.Cmd 15 | 16 | name string 17 | detailed bool 18 | } 19 | 20 | func (c *GetEndpoints) DefineCommand(...cmdparser.CommandOptions) { 21 | options := cmdparser.CommandOptions{ 22 | Use: "get-endpoints", 23 | Short: localizer.Sprintf("Display one or many endpoints from the sqlconfig file"), 24 | Examples: []cmdparser.ExampleOptions{ 25 | { 26 | Description: localizer.Sprintf("List all the endpoints in your sqlconfig file"), 27 | Steps: []string{"sqlcmd config get-endpoints"}}, 28 | { 29 | Description: localizer.Sprintf("List all the endpoints in your sqlconfig file"), 30 | Steps: []string{"sqlcmd config get-endpoints --detailed"}}, 31 | { 32 | Description: localizer.Sprintf("Describe one endpoint in your sqlconfig file"), 33 | Steps: []string{"sqlcmd config get-endpoints my-endpoint"}}, 34 | }, 35 | Run: c.run, 36 | FirstArgAlternativeForFlag: &cmdparser.AlternativeForFlagOptions{Flag: "name", Value: &c.name}, 37 | } 38 | 39 | c.Cmd.DefineCommand(options) 40 | 41 | c.AddFlag(cmdparser.FlagOptions{ 42 | String: &c.name, 43 | Name: "name", 44 | Usage: localizer.Sprintf("Endpoint name to view details of")}) 45 | 46 | c.AddFlag(cmdparser.FlagOptions{ 47 | Bool: &c.detailed, 48 | Name: "detailed", 49 | Usage: localizer.Sprintf("Include endpoint details")}) 50 | } 51 | 52 | func (c *GetEndpoints) run() { 53 | output := c.Output() 54 | 55 | if c.name != "" { 56 | if config.EndpointExists(c.name) { 57 | context := config.GetEndpoint(c.name) 58 | output.Struct(context) 59 | } else { 60 | output.FatalWithHints( 61 | []string{localizer.Sprintf("To view available endpoints run `%s`", localizer.GetEndpointsCommand)}, 62 | localizer.Sprintf("error: no endpoint exists with the name: \"%v\"", c.name)) 63 | } 64 | } else { 65 | config.OutputEndpoints(output.Struct, c.detailed) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /cmd/modern/root/config/get-endpoints_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func TestGetEndpoints(t *testing.T) { 13 | cmdparser.TestSetup(t) 14 | cmdparser.TestCmd[*AddEndpoint]("--name endpoint") 15 | cmdparser.TestCmd[*GetEndpoints]() 16 | cmdparser.TestCmd[*GetEndpoints]("endpoint") 17 | 18 | } 19 | 20 | func TestNegGetEndpoints(t *testing.T) { 21 | cmdparser.TestSetup(t) 22 | assert.Panics(t, func() { 23 | cmdparser.TestCmd[*GetEndpoints]("does-not-exist") 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /cmd/modern/root/config/get-users.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/microsoft/go-sqlcmd/internal/config" 9 | "github.com/microsoft/go-sqlcmd/internal/localizer" 10 | ) 11 | 12 | // GetUsers implements the `sqlcmd config get-users` command 13 | type GetUsers struct { 14 | cmdparser.Cmd 15 | 16 | name string 17 | detailed bool 18 | } 19 | 20 | func (c *GetUsers) DefineCommand(...cmdparser.CommandOptions) { 21 | options := cmdparser.CommandOptions{ 22 | Use: "get-users", 23 | Short: localizer.Sprintf("Display one or many users from the sqlconfig file"), 24 | Examples: []cmdparser.ExampleOptions{ 25 | { 26 | Description: localizer.Sprintf("List all the users in your sqlconfig file"), 27 | Steps: []string{"sqlcmd config get-users"}, 28 | }, 29 | { 30 | Description: localizer.Sprintf("List all the users in your sqlconfig file"), 31 | Steps: []string{"sqlcmd config get-users --detailed"}, 32 | }, 33 | { 34 | Description: localizer.Sprintf("Describe one user in your sqlconfig file"), 35 | Steps: []string{"sqlcmd config get-users user1"}, 36 | }, 37 | }, 38 | Run: c.run, 39 | 40 | FirstArgAlternativeForFlag: &cmdparser.AlternativeForFlagOptions{Flag: "name", Value: &c.name}, 41 | } 42 | 43 | c.Cmd.DefineCommand(options) 44 | 45 | c.AddFlag(cmdparser.FlagOptions{ 46 | String: &c.name, 47 | Name: "name", 48 | Usage: localizer.Sprintf("User name to view details of")}) 49 | 50 | c.AddFlag(cmdparser.FlagOptions{ 51 | Bool: &c.detailed, 52 | Name: "detailed", 53 | Usage: localizer.Sprintf("Include user details")}) 54 | } 55 | 56 | func (c *GetUsers) run() { 57 | output := c.Output() 58 | 59 | if c.name != "" { 60 | if config.UserNameExists(c.name) { 61 | user := config.GetUser(c.name) 62 | output.Struct(user) 63 | } else { 64 | output.FatalWithHints( 65 | []string{localizer.Sprintf("To view available users run `%s`", localizer.GetUsersCommand)}, 66 | localizer.Sprintf("error: no user exists with the name: \"%v\"", c.name)) 67 | } 68 | } else { 69 | config.OutputUsers(output.Struct, c.detailed) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /cmd/modern/root/config/get-users_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/stretchr/testify/assert" 9 | "os" 10 | "testing" 11 | ) 12 | 13 | func TestGetUsers(t *testing.T) { 14 | if os.Getenv("SQLCMD_PASSWORD") == "" { 15 | os.Setenv("SQLCMD_PASSWORD", "whatever") 16 | defer os.Setenv("SQLCMD_PASSWORD", "") 17 | } 18 | 19 | cmdparser.TestSetup(t) 20 | cmdparser.TestCmd[*AddUser]("--name user --username user --password-encryption none") 21 | cmdparser.TestCmd[*GetUsers]() 22 | cmdparser.TestCmd[*GetUsers]("user") 23 | } 24 | 25 | func TestNegGetUsers(t *testing.T) { 26 | cmdparser.TestSetup(t) 27 | assert.Panics(t, func() { 28 | cmdparser.TestCmd[*GetUsers]("does-not-exist") 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /cmd/modern/root/config/use-context.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/microsoft/go-sqlcmd/internal/config" 9 | "github.com/microsoft/go-sqlcmd/internal/localizer" 10 | ) 11 | 12 | // UseContext implements the `sqlcmd config use-context` command 13 | type UseContext struct { 14 | cmdparser.Cmd 15 | 16 | name string 17 | } 18 | 19 | func (c *UseContext) DefineCommand(...cmdparser.CommandOptions) { 20 | options := cmdparser.CommandOptions{ 21 | Use: "use-context", 22 | Short: localizer.Sprintf("Set the current context"), 23 | Examples: []cmdparser.ExampleOptions{{ 24 | Description: localizer.Sprintf("Set the mssql context (endpoint/user) to be the current context"), 25 | Steps: []string{"sqlcmd config use-context mssql"}}}, 26 | Aliases: []string{"use", "change-context", "set-context"}, 27 | Run: c.run, 28 | 29 | FirstArgAlternativeForFlag: &cmdparser.AlternativeForFlagOptions{Flag: "name", Value: &c.name}, 30 | } 31 | 32 | c.Cmd.DefineCommand(options) 33 | 34 | c.AddFlag(cmdparser.FlagOptions{ 35 | String: &c.name, 36 | Name: "name", 37 | Usage: localizer.Sprintf("Name of context to set as current context")}) 38 | } 39 | 40 | func (c *UseContext) run() { 41 | output := c.Output() 42 | 43 | if config.ContextExists(c.name) { 44 | config.SetCurrentContextName(c.name) 45 | output.InfoWithHints([]string{ 46 | localizer.Sprintf("To run a query: %s", localizer.RunQueryExample), 47 | localizer.Sprintf("To remove: %s", localizer.UninstallCommand)}, 48 | localizer.Sprintf("Switched to context \"%v\".", c.name)) 49 | } else { 50 | output.FatalWithHints([]string{localizer.Sprintf("To view available contexts run `%s`", localizer.GetContextCommand)}, 51 | localizer.Sprintf("No context exists with the name: \"%v\"", c.name)) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /cmd/modern/root/config/use-context_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func TestUseContext(t *testing.T) { 13 | cmdparser.TestSetup(t) 14 | cmdparser.TestCmd[*AddEndpoint]() 15 | cmdparser.TestCmd[*AddContext]("--endpoint endpoint") 16 | cmdparser.TestCmd[*UseContext]("--name context") 17 | } 18 | 19 | func TestNegUseContext(t *testing.T) { 20 | cmdparser.TestSetup(t) 21 | assert.Panics(t, func() { 22 | cmdparser.TestCmd[*UseContext]("does-not-exist") 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /cmd/modern/root/config/view.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/microsoft/go-sqlcmd/internal/config" 9 | "github.com/microsoft/go-sqlcmd/internal/localizer" 10 | ) 11 | 12 | // View implements the `sqlcmd config view` command 13 | type View struct { 14 | cmdparser.Cmd 15 | 16 | raw bool 17 | } 18 | 19 | func (c *View) DefineCommand(...cmdparser.CommandOptions) { 20 | options := cmdparser.CommandOptions{ 21 | Use: "view", 22 | Short: localizer.Sprintf("Display merged sqlconfig settings or a specified sqlconfig file"), 23 | Examples: []cmdparser.ExampleOptions{ 24 | { 25 | Description: localizer.Sprintf("Show sqlconfig settings, with REDACTED authentication data"), 26 | Steps: []string{"sqlcmd config view"}, 27 | }, 28 | { 29 | Description: localizer.Sprintf("Show sqlconfig settings and raw authentication data"), 30 | Steps: []string{"sqlcmd config view --raw"}, 31 | }, 32 | }, 33 | Aliases: []string{"show"}, 34 | Run: c.run, 35 | } 36 | 37 | c.Cmd.DefineCommand(options) 38 | 39 | c.AddFlag(cmdparser.FlagOptions{ 40 | Name: "raw", 41 | Bool: &c.raw, 42 | Usage: localizer.Sprintf("Display raw byte data"), 43 | }) 44 | } 45 | 46 | func (c *View) run() { 47 | output := c.Output() 48 | 49 | contents := config.RedactedConfig(c.raw) 50 | output.Struct(contents) 51 | } 52 | -------------------------------------------------------------------------------- /cmd/modern/root/config/view_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "testing" 9 | ) 10 | 11 | func TestView(t *testing.T) { 12 | cmdparser.TestSetup(t) 13 | cmdparser.TestCmd[*View]() 14 | } 15 | -------------------------------------------------------------------------------- /cmd/modern/root/config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package root 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "testing" 9 | ) 10 | 11 | // TestConfig runs a sanity test of `sqlcmd config` 12 | func TestConfig(t *testing.T) { 13 | cmdparser.TestSetup(t) 14 | cmdparser.TestCmd[*Config]() 15 | } 16 | -------------------------------------------------------------------------------- /cmd/modern/root/install.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package root 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/cmd/modern/root/install" 8 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 9 | "github.com/microsoft/go-sqlcmd/internal/localizer" 10 | ) 11 | 12 | // Install defines the `sqlcmd install` sub-commands 13 | type Install struct { 14 | cmdparser.Cmd 15 | } 16 | 17 | func (c *Install) DefineCommand(...cmdparser.CommandOptions) { 18 | options := cmdparser.CommandOptions{ 19 | Use: "create", 20 | Short: localizer.Sprintf("Install/Create SQL Server, Azure SQL, and Tools"), 21 | Aliases: []string{"install"}, 22 | SubCommands: c.SubCommands(), 23 | } 24 | 25 | c.Cmd.DefineCommand(options) 26 | } 27 | 28 | // SubCommands sets up the sub-commands for `sqlcmd install` such as 29 | // `sqlcmd install mssql` and `sqlcmd install azsql-edge` 30 | func (c *Install) SubCommands() []cmdparser.Command { 31 | dependencies := c.Dependencies() 32 | 33 | return []cmdparser.Command{ 34 | cmdparser.New[*install.Mssql](dependencies), 35 | cmdparser.New[*install.Edge](dependencies), 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cmd/modern/root/install/edge.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package install 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/cmd/modern/root/install/edge" 8 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 9 | "github.com/microsoft/go-sqlcmd/internal/cmdparser/dependency" 10 | "github.com/microsoft/go-sqlcmd/internal/localizer" 11 | "github.com/microsoft/go-sqlcmd/internal/pal" 12 | ) 13 | 14 | // Edge implements the `sqlcmd install azsql-edge command and sub-commands 15 | type Edge struct { 16 | cmdparser.Cmd 17 | MssqlBase 18 | } 19 | 20 | func (c *Edge) DefineCommand(...cmdparser.CommandOptions) { 21 | const repo = "azure-sql-edge" 22 | 23 | options := cmdparser.CommandOptions{ 24 | Use: "azsql-edge", 25 | Short: localizer.Sprintf("Install Azure Sql Edge"), 26 | Examples: []cmdparser.ExampleOptions{{ 27 | Description: localizer.Sprintf("Install/Create Azure SQL Edge in a container"), 28 | Steps: []string{"sqlcmd create azsql-edge"}}}, 29 | Run: c.MssqlBase.Run, 30 | SubCommands: c.SubCommands(), 31 | } 32 | 33 | c.MssqlBase.SetCrossCuttingConcerns(dependency.Options{ 34 | EndOfLine: pal.LineBreak(), 35 | Output: c.Output(), 36 | }) 37 | 38 | c.Cmd.DefineCommand(options) 39 | c.AddFlags(c.AddFlag, repo, "edge") 40 | } 41 | 42 | func (c *Edge) SubCommands() []cmdparser.Command { 43 | return []cmdparser.Command{ 44 | cmdparser.New[*edge.GetTags](c.Dependencies()), 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cmd/modern/root/install/edge/get-tags.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package edge 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/microsoft/go-sqlcmd/internal/container" 9 | "github.com/microsoft/go-sqlcmd/internal/localizer" 10 | ) 11 | 12 | type GetTags struct { 13 | cmdparser.Cmd 14 | } 15 | 16 | func (c *GetTags) DefineCommand(...cmdparser.CommandOptions) { 17 | options := cmdparser.CommandOptions{ 18 | Use: "get-tags", 19 | Short: localizer.Sprintf("Get tags available for Azure SQL Edge install"), 20 | Examples: []cmdparser.ExampleOptions{ 21 | { 22 | Description: localizer.Sprintf("List tags"), 23 | Steps: []string{"sqlcmd create azsql-edge get-tags"}, 24 | }, 25 | }, 26 | Aliases: []string{"gt", "lt"}, 27 | Run: c.run, 28 | } 29 | 30 | c.Cmd.DefineCommand(options) 31 | } 32 | 33 | func (c *GetTags) run() { 34 | output := c.Output() 35 | 36 | tags := container.ListTags( 37 | "azure-sql-edge", 38 | "https://mcr.microsoft.com", 39 | ) 40 | output.Struct(tags) 41 | } 42 | -------------------------------------------------------------------------------- /cmd/modern/root/install/edge/get-tags_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package edge 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "testing" 9 | ) 10 | 11 | func TestEdgeGetTags(t *testing.T) { 12 | cmdparser.TestSetup(t) 13 | cmdparser.TestCmd[*GetTags]() 14 | } 15 | -------------------------------------------------------------------------------- /cmd/modern/root/install/edge_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package install 5 | 6 | import ( 7 | "fmt" 8 | "github.com/microsoft/go-sqlcmd/cmd/modern/root/install/edge" 9 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 10 | "github.com/microsoft/go-sqlcmd/internal/config" 11 | "github.com/microsoft/go-sqlcmd/internal/container" 12 | "github.com/stretchr/testify/assert" 13 | "testing" 14 | ) 15 | 16 | func TestInstallEdge(t *testing.T) { 17 | // DEVNOTE: To prevent "import cycle not allowed" golang compile time error (due to 18 | // cleaning up the Install using root.Uninstall), we don't use root.Uninstall, 19 | // and use the controller object instead 20 | 21 | const registry = "docker.io" 22 | const repo = "library/hello-world" 23 | 24 | cmdparser.TestSetup(t) 25 | cmdparser.TestCmd[*edge.GetTags]() 26 | cmdparser.TestCmd[*Edge]( 27 | fmt.Sprintf( 28 | `--accept-eula --user-database foo --errorlog-wait-line "Hello from Docker!" --registry %v --repo %v`, 29 | registry, 30 | repo)) 31 | 32 | controller := container.NewController() 33 | id := config.ContainerId() 34 | err := controller.ContainerStop(id) 35 | assert.Nil(t, err) 36 | err = controller.ContainerRemove(id) 37 | assert.Nil(t, err) 38 | } 39 | -------------------------------------------------------------------------------- /cmd/modern/root/install/mssql/get-tags.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package mssql 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/microsoft/go-sqlcmd/internal/container" 9 | "github.com/microsoft/go-sqlcmd/internal/localizer" 10 | ) 11 | 12 | type GetTags struct { 13 | cmdparser.Cmd 14 | } 15 | 16 | func (c *GetTags) DefineCommand(...cmdparser.CommandOptions) { 17 | options := cmdparser.CommandOptions{ 18 | Use: "get-tags", 19 | Short: localizer.Sprintf("Get tags available for mssql install"), 20 | Examples: []cmdparser.ExampleOptions{ 21 | { 22 | Description: localizer.Sprintf("List tags"), 23 | Steps: []string{"sqlcmd create mssql get-tags"}, 24 | }, 25 | }, 26 | Aliases: []string{"gt", "lt"}, 27 | Run: c.run, 28 | } 29 | 30 | c.Cmd.DefineCommand(options) 31 | 32 | } 33 | 34 | func (c *GetTags) run() { 35 | output := c.Output() 36 | 37 | tags := container.ListTags( 38 | "mssql/server", 39 | "https://mcr.microsoft.com", 40 | ) 41 | output.Struct(tags) 42 | } 43 | -------------------------------------------------------------------------------- /cmd/modern/root/install/mssql/get-tags_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package mssql 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "testing" 9 | ) 10 | 11 | func TestEdgeGetTags(t *testing.T) { 12 | cmdparser.TestSetup(t) 13 | cmdparser.TestCmd[*GetTags]() 14 | } 15 | -------------------------------------------------------------------------------- /cmd/modern/root/install_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package root 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "testing" 9 | ) 10 | 11 | // TestInstall runs a sanity test of `sqlcmd install` 12 | func TestInstall(t *testing.T) { 13 | cmdparser.TestSetup(t) 14 | cmdparser.TestCmd[*Install]() 15 | } 16 | -------------------------------------------------------------------------------- /cmd/modern/root/open.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package root 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/cmd/modern/root/open" 8 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 9 | "github.com/microsoft/go-sqlcmd/internal/localizer" 10 | ) 11 | 12 | // Open defines the `sqlcmd open` sub-commands 13 | type Open struct { 14 | cmdparser.Cmd 15 | } 16 | 17 | func (c *Open) DefineCommand(...cmdparser.CommandOptions) { 18 | options := cmdparser.CommandOptions{ 19 | Use: "open", 20 | Short: localizer.Sprintf("Open tools (e.g Azure Data Studio) for current context"), 21 | SubCommands: c.SubCommands(), 22 | } 23 | 24 | c.Cmd.DefineCommand(options) 25 | } 26 | 27 | // SubCommands sets up the sub-commands for `sqlcmd open` such as 28 | // `sqlcmd open ads` 29 | func (c *Open) SubCommands() []cmdparser.Command { 30 | dependencies := c.Dependencies() 31 | 32 | return []cmdparser.Command{ 33 | cmdparser.New[*open.Ads](dependencies), 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /cmd/modern/root/open/ads_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package open 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig" 8 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 9 | "github.com/microsoft/go-sqlcmd/internal/localizer" 10 | ) 11 | 12 | // Type Ads is used to implement the "open ads" which launches Azure 13 | // Data Studio and establishes a connection to the SQL Server for the current 14 | // context 15 | type Ads struct { 16 | cmdparser.Cmd 17 | } 18 | 19 | func (c *Ads) persistCredentialForAds(hostname string, endpoint sqlconfig.Endpoint, user *sqlconfig.User) { 20 | // UNDONE: See - https://github.com/microsoft/go-sqlcmd/issues/257 21 | } 22 | 23 | // BUG(stuartpa): There is a bug in ADS that is naming credentials in Mac KeyChain 24 | // using UTF16 encoding, when it should be UTF8. This prevents us from creating 25 | // an item in KeyChain that ADS can then re-use (because all the golang Keychain 26 | // packages take a string for credential name, which is UTF8). Rather than trying 27 | // to clone the ADS bug here, we prompt the user without to get the credential which 28 | // they'll have to enter into ADS (once, if they save password in the connection dialog) 29 | func (c *Ads) displayPreLaunchInfo() { 30 | output := c.Output() 31 | 32 | output.Info(localizer.Sprintf("Temporary: To view connection information run:")) 33 | output.Info("") 34 | output.Info("\tsqlcmd config connection-strings") 35 | output.Info("") 36 | output.Info("(see issue for more information: https://github.com/microsoft/go-sqlcmd/issues/257)") 37 | } 38 | -------------------------------------------------------------------------------- /cmd/modern/root/open/ads_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package open 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig" 8 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 9 | ) 10 | 11 | // Type Ads is used to implement the "open ads" which launches Azure 12 | // Data Studio and establishes a connection to the SQL Server for the current 13 | // context 14 | type Ads struct { 15 | cmdparser.Cmd 16 | } 17 | 18 | func (c *Ads) persistCredentialForAds(hostname string, endpoint sqlconfig.Endpoint, user *sqlconfig.User) { 19 | panic("not implemented") 20 | } 21 | 22 | func (c *Ads) displayPreLaunchInfo() { 23 | panic("not implemented") 24 | } 25 | -------------------------------------------------------------------------------- /cmd/modern/root/open/ads_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package open 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig" 8 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 9 | "github.com/microsoft/go-sqlcmd/internal/config" 10 | "runtime" 11 | "testing" 12 | ) 13 | 14 | // TestOpen runs a sanity test of `sqlcmd open` 15 | func TestAds(t *testing.T) { 16 | if runtime.GOOS != "windows" { 17 | t.Skip("Ads support only on Windows at this time") 18 | } 19 | 20 | cmdparser.TestSetup(t) 21 | config.AddEndpoint(sqlconfig.Endpoint{ 22 | AssetDetails: nil, 23 | EndpointDetails: sqlconfig.EndpointDetails{}, 24 | Name: "endpoint", 25 | }) 26 | config.AddContext(sqlconfig.Context{ 27 | ContextDetails: sqlconfig.ContextDetails{ 28 | Endpoint: "endpoint", 29 | User: nil, 30 | }, 31 | Name: "context", 32 | }) 33 | config.SetCurrentContextName("context") 34 | 35 | cmdparser.TestCmd[*Ads]() 36 | } 37 | -------------------------------------------------------------------------------- /cmd/modern/root/open/ads_windows_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package open 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig" 8 | "github.com/microsoft/go-sqlcmd/internal/cmdparser/dependency" 9 | "github.com/microsoft/go-sqlcmd/internal/credman" 10 | "github.com/microsoft/go-sqlcmd/internal/output" 11 | "github.com/microsoft/go-sqlcmd/internal/secret" 12 | "github.com/stretchr/testify/assert" 13 | "testing" 14 | ) 15 | 16 | func TestPersistCredentialForAds(t *testing.T) { 17 | ads := Ads{} 18 | ads.SetCrossCuttingConcerns(dependency.Options{ 19 | EndOfLine: "", 20 | Output: output.New(output.Options{}), 21 | }) 22 | 23 | user := &sqlconfig.User{ 24 | BasicAuth: &sqlconfig.BasicAuthDetails{ 25 | Username: "testuser", 26 | Password: "testpass", 27 | PasswordEncryption: "none", 28 | }, 29 | } 30 | ads.persistCredentialForAds("localhost", sqlconfig.Endpoint{ 31 | EndpointDetails: sqlconfig.EndpointDetails{ 32 | Port: 1433, 33 | }, 34 | }, user) 35 | 36 | // Test if the correct target name is generated 37 | expectedTargetName := "Microsoft.SqlTools|itemtype:Profile|id:providerName:MSSQL|applicationName:azdata|authenticationType:SqlLogin|database:|server:localhost,1433|user:testuser" 38 | assert.Equal(t, ads.credential.TargetName, expectedTargetName, "Expected target name to be %s, got %s", expectedTargetName, ads.credential.TargetName) 39 | assert.Equal(t, ads.credential.UserName, user.BasicAuth.Username, "Expected username to be %s, got %s", user.BasicAuth.Username, ads.credential.UserName) 40 | 41 | // Test if the password is decoded correctly 42 | decodedPassword := secret.DecodeAsUtf16(user.BasicAuth.Password, user.BasicAuth.PasswordEncryption) 43 | assert.Equal( 44 | t, 45 | ads.credential.CredentialBlob, 46 | decodedPassword, 47 | "Expected decoded password to be %v, got %v", 48 | decodedPassword, 49 | ads.credential.CredentialBlob, 50 | ) 51 | } 52 | 53 | func TestRemovePreviousCredential(t *testing.T) { 54 | ads := Ads{} 55 | ads.SetCrossCuttingConcerns(dependency.Options{ 56 | EndOfLine: "", 57 | Output: output.New(output.Options{}), 58 | }) 59 | 60 | ads.credential = credman.Credential{ 61 | TargetName: "TestTargetName", 62 | Persist: credman.PersistSession, 63 | } 64 | 65 | ads.writeCredential() 66 | ads.removePreviousCredential() 67 | } 68 | -------------------------------------------------------------------------------- /cmd/modern/root/open_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package root 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "testing" 9 | ) 10 | 11 | // TestOpen runs a sanity test of `sqlcmd open` 12 | func TestOpen(t *testing.T) { 13 | cmdparser.TestSetup(t) 14 | cmdparser.TestCmd[*Open]() 15 | } 16 | -------------------------------------------------------------------------------- /cmd/modern/root/query_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package root 5 | 6 | import ( 7 | "fmt" 8 | "github.com/microsoft/go-sqlcmd/cmd/modern/root/config" 9 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 10 | "os" 11 | "runtime" 12 | "testing" 13 | ) 14 | 15 | // TestQuery runs a sanity test of `sqlcmd query` using the local instance on 1433 16 | func TestQuery(t *testing.T) { 17 | if runtime.GOOS != "windows" { 18 | t.Skip("stuartpa: This is failing in the pipeline (Login failed for user 'sa'.)") 19 | } 20 | 21 | cmdparser.TestSetup(t) 22 | 23 | setupContext(t) 24 | cmdparser.TestCmd[*Query]("PRINT") 25 | } 26 | 27 | func TestQueryWithNonDefaultDatabase(t *testing.T) { 28 | if runtime.GOOS != "windows" { 29 | t.Skip("stuartpa: This is failing in the pipeline (Login failed for user 'sa'.)") 30 | } 31 | 32 | cmdparser.TestSetup(t) 33 | 34 | setupContext(t) 35 | cmdparser.TestCmd[*Query](`--text "PRINT DB_NAME()" --database master`) 36 | 37 | // TODO: Add test validation that DB name was actually master! 38 | } 39 | 40 | func setupContext(t *testing.T) { 41 | // if SQLCMDSERVER != "" add an endpoint using the --address 42 | if os.Getenv("SQLCMDSERVER") == "" { 43 | cmdparser.TestCmd[*config.AddEndpoint]() 44 | } else { 45 | t.Logf("SQLCMDSERVER: %v", os.Getenv("SQLCMDSERVER")) 46 | cmdparser.TestCmd[*config.AddEndpoint](fmt.Sprintf("--address %v", os.Getenv("SQLCMDSERVER"))) 47 | } 48 | 49 | // If the SQLCMDPASSWORD envvar is set, then add a basic user, otherwise 50 | // we'll use trusted auth 51 | if os.Getenv("SQLCMDPASSWORD") != "" && 52 | os.Getenv("SQLCMDUSER") != "" { 53 | 54 | cmdparser.TestCmd[*config.AddUser]( 55 | fmt.Sprintf("--name user1 --username %s", 56 | os.Getenv("SQLCMDUSER"))) 57 | cmdparser.TestCmd[*config.AddContext]("--endpoint endpoint --user user1") 58 | } else { 59 | cmdparser.TestCmd[*config.AddContext]("--endpoint endpoint") 60 | } 61 | cmdparser.TestCmd[*config.View]() // displaying the config (info in-case test fails) 62 | } 63 | -------------------------------------------------------------------------------- /cmd/modern/root/start.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package root 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/microsoft/go-sqlcmd/internal/config" 9 | "github.com/microsoft/go-sqlcmd/internal/container" 10 | "github.com/microsoft/go-sqlcmd/internal/localizer" 11 | ) 12 | 13 | type Start struct { 14 | cmdparser.Cmd 15 | } 16 | 17 | func (c *Start) DefineCommand(...cmdparser.CommandOptions) { 18 | options := cmdparser.CommandOptions{ 19 | Use: "start", 20 | Short: localizer.Sprintf("Start current context"), 21 | Examples: []cmdparser.ExampleOptions{ 22 | { 23 | Description: localizer.Sprintf("Start the current context"), 24 | Steps: []string{`sqlcmd start`}}, 25 | }, 26 | Run: c.run, 27 | } 28 | 29 | c.Cmd.DefineCommand(options) 30 | } 31 | 32 | func (c *Start) run() { 33 | output := c.Output() 34 | 35 | if config.CurrentContextName() == "" { 36 | output.FatalWithHintExamples([][]string{ 37 | {localizer.Sprintf("To view available contexts"), "sqlcmd config get-contexts"}, 38 | }, localizer.Sprintf("No current context")) 39 | } 40 | if config.CurrentContextEndpointHasContainer() { 41 | controller := container.NewController() 42 | id := config.ContainerId() 43 | endpoint, _ := config.CurrentContext() 44 | 45 | output.Info(localizer.Sprintf("Starting %q for context %q", endpoint.ContainerDetails.Image, config.CurrentContextName())) 46 | err := controller.ContainerStart(id) 47 | c.CheckErr(err) 48 | } else { 49 | output.FatalWithHintExamples([][]string{ 50 | {localizer.Sprintf("Create new context with a sql container "), "sqlcmd create mssql"}, 51 | }, localizer.Sprintf("Current context does not have a container")) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /cmd/modern/root/start_test.go: -------------------------------------------------------------------------------- 1 | package root 2 | 3 | import ( 4 | "github.com/microsoft/go-sqlcmd/cmd/modern/root/config" 5 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | // TestNegStart tests that the `sqlcmd start` command fails when 11 | // no context is defined 12 | func TestNegStart(t *testing.T) { 13 | cmdparser.TestSetup(t) 14 | assert.Panics(t, func() { 15 | cmdparser.TestCmd[*Start]() 16 | }) 17 | } 18 | 19 | // TestNegStart2 tests that the `sqlcmd start` command fails when 20 | // no container is included in endpoint 21 | func TestNegStart2(t *testing.T) { 22 | cmdparser.TestSetup(t) 23 | cmdparser.TestCmd[*config.AddEndpoint]() 24 | cmdparser.TestCmd[*config.AddContext]("--endpoint endpoint") 25 | assert.Panics(t, func() { 26 | cmdparser.TestCmd[*Start]() 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /cmd/modern/root/stop.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package root 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/microsoft/go-sqlcmd/internal/config" 9 | "github.com/microsoft/go-sqlcmd/internal/container" 10 | "github.com/microsoft/go-sqlcmd/internal/localizer" 11 | ) 12 | 13 | type Stop struct { 14 | cmdparser.Cmd 15 | } 16 | 17 | func (c *Stop) DefineCommand(...cmdparser.CommandOptions) { 18 | options := cmdparser.CommandOptions{ 19 | Use: "stop", 20 | Short: localizer.Sprintf("Stop current context"), 21 | Examples: []cmdparser.ExampleOptions{ 22 | { 23 | Description: localizer.Sprintf("Stop the current context"), 24 | Steps: []string{`sqlcmd stop`}}, 25 | }, 26 | Run: c.run, 27 | } 28 | 29 | c.Cmd.DefineCommand(options) 30 | } 31 | 32 | func (c *Stop) run() { 33 | output := c.Output() 34 | 35 | if config.CurrentContextName() == "" { 36 | output.FatalWithHintExamples([][]string{ 37 | {localizer.Sprintf("To view available contexts"), "sqlcmd config get-contexts"}, 38 | }, localizer.Sprintf("No current context")) 39 | } 40 | if config.CurrentContextEndpointHasContainer() { 41 | controller := container.NewController() 42 | id := config.ContainerId() 43 | endpoint, _ := config.CurrentContext() 44 | 45 | output.Info(localizer.Sprintf("Stopping %q for context %q", endpoint.ContainerDetails.Image, config.CurrentContextName())) 46 | err := controller.ContainerStop(id) 47 | c.CheckErr(err) 48 | } else { 49 | output.FatalWithHintExamples([][]string{ 50 | {localizer.Sprintf("Create a new context with a SQL Server container "), "sqlcmd create mssql"}, 51 | }, localizer.Sprintf("Current context does not have a container")) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /cmd/modern/root/stop_test.go: -------------------------------------------------------------------------------- 1 | package root 2 | 3 | import ( 4 | "github.com/microsoft/go-sqlcmd/cmd/modern/root/config" 5 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | // TestNegStop tests that the `sqlcmd stop` command fails when 11 | // no context is defined 12 | func TestNegStop(t *testing.T) { 13 | cmdparser.TestSetup(t) 14 | assert.Panics(t, func() { 15 | cmdparser.TestCmd[*Stop]() 16 | }) 17 | } 18 | 19 | // TestNegStop2 tests that the `sqlcmd stop` command fails when 20 | // no container is included in endpoint 21 | func TestNegStop2(t *testing.T) { 22 | cmdparser.TestSetup(t) 23 | cmdparser.TestCmd[*config.AddEndpoint]() 24 | cmdparser.TestCmd[*config.AddContext]("--endpoint endpoint") 25 | assert.Panics(t, func() { 26 | cmdparser.TestCmd[*Stop]() 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /cmd/modern/root/uninstall_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package root 5 | 6 | import ( 7 | "fmt" 8 | "github.com/microsoft/go-sqlcmd/cmd/modern/root/install" 9 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 10 | "testing" 11 | ) 12 | 13 | // TestUninstallWithUserDbPresent installs Mssql 14 | // with a user database, and then uninstalls it using the --force option 15 | func TestUninstallWithUserDbPresent(t *testing.T) { 16 | const registry = "docker.io" 17 | const repo = "library/hello-world" 18 | 19 | cmdparser.TestSetup(t) 20 | 21 | cmdparser.TestCmd[*install.Edge]( 22 | fmt.Sprintf( 23 | `--accept-eula --port 1500 --errorlog-wait-line "Hello from Docker!" --registry %v --repo %v`, 24 | registry, 25 | repo)) 26 | cmdparser.TestCmd[*Stop]() 27 | cmdparser.TestCmd[*Start]() 28 | cmdparser.TestCmd[*Uninstall]("--yes --force") 29 | } 30 | -------------------------------------------------------------------------------- /cmd/modern/root_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package main 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser" 8 | "github.com/microsoft/go-sqlcmd/internal/cmdparser/dependency" 9 | "github.com/stretchr/testify/assert" 10 | "testing" 11 | ) 12 | 13 | // TestRoot is a quick sanity test 14 | func TestRoot(t *testing.T) { 15 | c := cmdparser.New[*Root](dependency.Options{}) 16 | c.DefineCommand() 17 | c.SetArgsForUnitTesting([]string{}) 18 | c.Execute() 19 | } 20 | 21 | func TestIsValidSubCommand(t *testing.T) { 22 | c := cmdparser.New[*Root](dependency.Options{}) 23 | invalid := c.IsValidSubCommand("nope") 24 | assert.Equal(t, false, invalid) 25 | valid := c.IsValidSubCommand("query") 26 | assert.Equal(t, true, valid) 27 | } 28 | -------------------------------------------------------------------------------- /cmd/modern/sqlconfig/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sqlconfig 5 | 6 | /* 7 | Package sqlconfig defines the schema for the sqlconfig file. The sqlconfig file 8 | by default resides in the folder: 9 | 10 | Windows: %USERPROFILE%\.sqlcmd\sqlconfig 11 | *nix: ~/.sqlcmd/sqlconfig 12 | 13 | The sqlconfig contains Contexts. A context is named (e.g. mssql2) and 14 | contains the Endpoint details (to connect to) and User details (to 15 | use for authentication with the endpoint. 16 | 17 | If there is more than one context defined, there is always a "currentcontext", 18 | the currentcontext can be changed using 19 | 20 | sqlcmd config use-context CONTEXT_NAME 21 | 22 | # Example 23 | 24 | An example of the sqlconfig file looks like this: 25 | 26 | apiversion: v1 27 | endpoints: 28 | - asset: 29 | - container: 30 | id: 0e698e65e19d9c 31 | image: mcr.microsoft.com/mssql/server:2022-latest 32 | endpoint: 33 | address: 127.0.0.1 34 | port: 1435 35 | name: mssql 36 | contexts: 37 | - context: 38 | endpoint: mssql 39 | user: your-alias@mssql 40 | name: mssql 41 | currentcontext: mssql 42 | kind: Config 43 | users: 44 | - user: 45 | username: your-alias 46 | password: REDACTED 47 | name: your-alias@mssql 48 | 49 | # Security 50 | 51 | - OnWindows the password is encrypted using the DPAPI. 52 | - TODO: On MacOS the password will be encrypted using the KeyChain 53 | 54 | The password is also base64 encoded. 55 | 56 | To view the decrypted and (base64) decoded passwords run 57 | 58 | sqlcmd config view --raw 59 | */ 60 | -------------------------------------------------------------------------------- /cmd/modern/sqlconfig/sqlconfig.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | // Package sqlconfig, defines the metadata for representing a sqlconfig file. 5 | // It includes structs for representing an endpoint, context, user, and the overall 6 | // sqlconfig file itself. Each struct has fields for storing the various pieces 7 | // of information that make up an SQL configuration, such as endpoint address 8 | // and port, context name and endpoint, and user authentication type and details. 9 | // These structs are used to manage and manipulate the sqlconfig. 10 | package sqlconfig 11 | 12 | type EndpointDetails struct { 13 | Address string `mapstructure:"address"` 14 | Port int `mapstructure:"port"` 15 | } 16 | 17 | type ContainerDetails struct { 18 | Id string `mapstructure:"id"` 19 | Image string `mapstructure:"image"` 20 | } 21 | 22 | type AssetDetails struct { 23 | *ContainerDetails `mapstructure:"container,omitempty" yaml:"container,omitempty"` 24 | } 25 | 26 | type Endpoint struct { 27 | *AssetDetails `mapstructure:"asset,omitempty" yaml:"asset,omitempty"` 28 | EndpointDetails `mapstructure:"endpoint" yaml:"endpoint"` 29 | Name string `mapstructure:"name"` 30 | } 31 | 32 | type ContextDetails struct { 33 | Endpoint string `mapstructure:"endpoint"` 34 | User *string `mapstructure:"user,omitempty" yaml:"user,omitempty"` 35 | } 36 | 37 | type Context struct { 38 | ContextDetails `mapstructure:"context" yaml:"context"` 39 | Name string `mapstructure:"name"` 40 | } 41 | 42 | type BasicAuthDetails struct { 43 | Username string `mapstructure:"username"` 44 | PasswordEncryption string `mapstructure:"password-encryption" yaml:"password-encryption"` 45 | Password string `mapstructure:"password"` 46 | } 47 | 48 | type User struct { 49 | Name string `mapstructure:"name"` 50 | AuthenticationType string `mapstructure:"authentication-type" yaml:"authentication-type"` 51 | BasicAuth *BasicAuthDetails `mapstructure:"basic-auth,omitempty" yaml:"basic-auth,omitempty"` 52 | } 53 | 54 | type Sqlconfig struct { 55 | Version string `mapstructure:"version"` 56 | Endpoints []Endpoint `mapstructure:"endpoints"` 57 | Contexts []Context `mapstructure:"contexts"` 58 | CurrentContext string `mapstructure:"currentcontext"` 59 | Users []User `mapstructure:"users"` 60 | } 61 | -------------------------------------------------------------------------------- /cmd/modern/winres/winres.json: -------------------------------------------------------------------------------- 1 | { 2 | "RT_GROUP_ICON": { 3 | "APP": { 4 | "0000" : "../../../release/windows/msi/resources/sqlcmd.ico" 5 | } 6 | }, 7 | "RT_VERSION": { 8 | "#1": { 9 | "0000": { 10 | "fixed": { 11 | "file_version": "0.0.0.0", 12 | "product_version": "0.0.0.0" 13 | }, 14 | "info": { 15 | "0409": { 16 | "Comments": "SQL", 17 | "CompanyName": "Microsoft Corporation", 18 | "FileDescription": "T-SQL execution command line utility", 19 | "FileVersion": "", 20 | "InternalName": "go-sqlcmd", 21 | "LegalCopyright": "Microsoft Corporation", 22 | "LegalTrademarks": "Microsoft SQL Server is a registered trademark of Microsoft Corporation", 23 | "OriginalFilename": "sqlcmd.exe", 24 | "PrivateBuild": "", 25 | "ProductName": "Microsoft SQL Server", 26 | "ProductVersion": "", 27 | "SpecialBuild": "" 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /cmd/sqlcmd-linter/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | sqlcmdlinter "github.com/microsoft/go-sqlcmd/pkg/sqlcmd-linter" 5 | "golang.org/x/tools/go/analysis/multichecker" 6 | ) 7 | 8 | func main() { 9 | multichecker.Main(sqlcmdlinter.AssertAnalyzer, sqlcmdlinter.ImportsAnalyzer) 10 | } 11 | -------------------------------------------------------------------------------- /cmd/sqlcmd/pipe_detection_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sqlcmd 5 | 6 | import ( 7 | "os" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestStdinPipeDetection(t *testing.T) { 14 | // Get stdin info 15 | fi, err := os.Stdin.Stat() 16 | assert.NoError(t, err, "os.Stdin.Stat()") 17 | 18 | // On most CI systems, stdin will be a pipe or file (not a terminal) 19 | // We're testing the logic, not expecting a specific result 20 | isPipe := false 21 | if fi != nil && (fi.Mode()&os.ModeCharDevice) == 0 { 22 | isPipe = true 23 | } 24 | 25 | // Just making sure the detection code doesn't crash 26 | // The actual value will depend on the environment 27 | t.Logf("Stdin detected as pipe: %v", isPipe) 28 | } -------------------------------------------------------------------------------- /cmd/sqlcmd/testdata/bad.sql: -------------------------------------------------------------------------------- 1 | select @@badbad -------------------------------------------------------------------------------- /cmd/sqlcmd/testdata/create100db.sql: -------------------------------------------------------------------------------- 1 | create database db100 2 | go 3 | 4 | create database db101 5 | go 6 | 7 | create database db102 8 | go 9 | create database db103 10 | go 11 | create database db104 12 | go 13 | create database db105 14 | go 15 | create database db106 16 | go 17 | create database db107 18 | go 19 | create database db108 20 | go 21 | create database db109 22 | go 23 | create database db110 24 | go 25 | create database db111 26 | go 27 | create database db112 28 | go 29 | create database db113 30 | go 31 | -------------------------------------------------------------------------------- /cmd/sqlcmd/testdata/drop100db.txt: -------------------------------------------------------------------------------- 1 | drop database db100 2 | go 3 | 4 | drop database db101 5 | go 6 | 7 | drop database db102 8 | go 9 | drop database db103 10 | go 11 | drop database db104 12 | go 13 | drop database db105 14 | go 15 | drop database db106 16 | go 17 | drop database db107 18 | go 19 | drop database db108 20 | go 21 | drop database db109 22 | go 23 | drop database db110 24 | go 25 | drop database db111 26 | go 27 | drop database db112 28 | go 29 | drop database db113 30 | go 31 | -------------------------------------------------------------------------------- /cmd/sqlcmd/testdata/select,100.sql: -------------------------------------------------------------------------------- 1 | select 100 2 | -------------------------------------------------------------------------------- /cmd/sqlcmd/testdata/select100.sql: -------------------------------------------------------------------------------- 1 | select 100 -------------------------------------------------------------------------------- /cmd/sqlcmd/testdata/selectunicode_BE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/go-sqlcmd/e8f9c267602cf2949f6b3ba2bfd7a47c891b7479/cmd/sqlcmd/testdata/selectunicode_BE.txt -------------------------------------------------------------------------------- /cmd/sqlcmd/testdata/selectunicode_LE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/go-sqlcmd/e8f9c267602cf2949f6b3ba2bfd7a47c891b7479/cmd/sqlcmd/testdata/selectunicode_LE.txt -------------------------------------------------------------------------------- /cmd/sqlcmd/testdata/selectutf8.txt: -------------------------------------------------------------------------------- 1 | select N'挨挨唉哀皑癌蔼矮' as SimplifiedChinese 2 | 3 | -------------------------------------------------------------------------------- /cmd/sqlcmd/testdata/selectutf8_bom.txt: -------------------------------------------------------------------------------- 1 | select N'挨挨唉哀皑癌蔼矮' as SimplifiedChinese 2 | 3 | -------------------------------------------------------------------------------- /cmd/sqlcmd/testdata/unicodeout.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/go-sqlcmd/e8f9c267602cf2949f6b3ba2bfd7a47c891b7479/cmd/sqlcmd/testdata/unicodeout.txt -------------------------------------------------------------------------------- /cmd/sqlcmd/testdata/unicodeout_linux.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/go-sqlcmd/e8f9c267602cf2949f6b3ba2bfd7a47c891b7479/cmd/sqlcmd/testdata/unicodeout_linux.txt -------------------------------------------------------------------------------- /cmd/sqlcmd/testdata/utf8out.txt: -------------------------------------------------------------------------------- 1 | SimplifiedChinese 2 | ----------------- 3 | 挨挨唉哀皑癌蔼矮 4 | 5 | (1 row affected) 6 | -------------------------------------------------------------------------------- /cmd/sqlcmd/testdata/utf8out_linux.txt: -------------------------------------------------------------------------------- 1 | SimplifiedChinese 2 | ----------------- 3 | 挨挨唉哀皑癌蔼矮 4 | 5 | (1 row affected) 6 | -------------------------------------------------------------------------------- /internal/buffer/memory-buffer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package buffer 5 | 6 | import "bytes" 7 | 8 | // MemoryBuffer has both Write and Close methods for use as io.WriteCloser 9 | // when testing (instead of os.Stdout), so tests can assert.Equal results etc. 10 | type MemoryBuffer struct { 11 | buf *bytes.Buffer 12 | } 13 | 14 | func (b *MemoryBuffer) Write(p []byte) (n int, err error) { 15 | return b.buf.Write(p) 16 | } 17 | 18 | func (b *MemoryBuffer) Close() error { 19 | b.buf = nil 20 | 21 | return nil 22 | } 23 | 24 | func (b *MemoryBuffer) String() string { 25 | return b.buf.String() 26 | } 27 | 28 | func NewMemoryBuffer() *MemoryBuffer { 29 | return &MemoryBuffer{buf: new(bytes.Buffer)} 30 | } 31 | -------------------------------------------------------------------------------- /internal/buffer/memory-buffer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package buffer 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestMemoryBuffer_Write(t *testing.T) { 13 | buffer := NewMemoryBuffer() 14 | _, err := buffer.Write([]byte("hello world")) 15 | assert.NoError(t, err) 16 | assert.Equal(t, "hello world", buffer.String()) 17 | } 18 | 19 | func TestMemoryBuffer_Close(t *testing.T) { 20 | buffer := NewMemoryBuffer() 21 | assert.NoError(t, buffer.Close()) 22 | } 23 | 24 | func TestMemoryBuffer_String(t *testing.T) { 25 | buffer := NewMemoryBuffer() 26 | _, err := buffer.Write([]byte("foo bar")) 27 | if err != nil { 28 | return 29 | } 30 | assert.Equal(t, "foo bar", buffer.String()) 31 | } 32 | -------------------------------------------------------------------------------- /internal/cmdparser/dependency/options.go: -------------------------------------------------------------------------------- 1 | package dependency 2 | 3 | import "github.com/microsoft/go-sqlcmd/internal/output" 4 | 5 | type Options struct { 6 | EndOfLine string 7 | Output *output.Output 8 | } 9 | -------------------------------------------------------------------------------- /internal/cmdparser/factory.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package cmdparser 5 | 6 | import ( 7 | "fmt" 8 | "github.com/microsoft/go-sqlcmd/internal/cmdparser/dependency" 9 | "github.com/microsoft/go-sqlcmd/internal/output" 10 | "github.com/spf13/cobra" 11 | "os" 12 | ) 13 | 14 | // Initialize runs the init func() after the command-line provided by the user 15 | // has been parsed. 16 | func Initialize(init func()) { 17 | cobra.OnInitialize(init) 18 | } 19 | 20 | func New[T PtrAsReceiverWrapper[pointerType], pointerType any](dependencies dependency.Options) (command T) { 21 | if dependencies.Output == nil { 22 | dependencies.Output = output.New(output.Options{ 23 | OutputType: "yaml", 24 | LoggingLevel: 2, 25 | StandardWriter: os.Stdout, 26 | ErrorHandler: func(err error) { 27 | if err != nil { 28 | panic(err) 29 | } 30 | }, 31 | HintHandler: func(hints []string) { fmt.Printf("HINTS: %v\n", hints) }}) 32 | } 33 | if dependencies.EndOfLine == "" { 34 | dependencies.EndOfLine = "\n" 35 | } 36 | 37 | command = new(pointerType) 38 | command.SetCrossCuttingConcerns(dependencies) 39 | command.DefineCommand() 40 | 41 | return 42 | } 43 | 44 | // PtrAsReceiverWrapper per golang design doc "an unfortunate necessary kludge": 45 | // https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#pointer-method-example 46 | // https://www.reddit.com/r/golang/comments/uqwh5d/generics_new_value_from_pointer_type_with/ 47 | type PtrAsReceiverWrapper[T any] interface { 48 | Command 49 | *T 50 | } 51 | -------------------------------------------------------------------------------- /internal/cmdparser/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package cmdparser 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser/dependency" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | // Command is an interface for defining and running a command which is 12 | // part of a command line program. Command contains methods for setting 13 | // command options, running the command, and checking for errors. 14 | type Command interface { 15 | // CheckErr checks if the given error is non-nil and, if it is, it prints the error 16 | // to the output and exits the program with an exit code of 1. 17 | CheckErr(error) 18 | 19 | // Command returns the underlying cobra.Command object for this command. 20 | // This is useful for defining subcommands. 21 | Command() *cobra.Command 22 | 23 | // DefineCommand is used to define a new command and its associated 24 | // options, flags, and subcommands. It takes in a CommandOptions 25 | // struct, which allow the caller to specify the command's name, description, 26 | // usage, and behavior. 27 | DefineCommand(...CommandOptions) 28 | 29 | // IsSubCommand is TEMPORARY code that will be removed when the 30 | // old Kong CLI is retired. It returns true if the command-line 31 | // provided by the user looks like they want the new cobra CLI, e.g. 32 | // sqlcmd query, sqlcmd install, sqlcmd --help etc. 33 | IsSubCommand(command string) bool 34 | 35 | // SetArgsForUnitTesting method allows a caller to set the arguments for the 36 | // command when running unit tests. This is useful because it allows the caller 37 | // to simulate different command-line input scenarios in their tests. 38 | SetArgsForUnitTesting(args []string) 39 | 40 | // SetCrossCuttingConcerns is used to inject cross-cutting concerns (i.e. dependencies) 41 | // into the Command object (like logging etc.). The dependency.Options allows 42 | // the Command object to have access to the dependencies it needs, without 43 | // having to manage them directly. 44 | SetCrossCuttingConcerns(dependency.Options) 45 | } 46 | -------------------------------------------------------------------------------- /internal/cmdparser/test_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package cmdparser 5 | 6 | // Test_test.go contains functions to test the functions in test.go, so this file 7 | // tests the test functions. This file shows end-to-end usage of how to create 8 | // the simplest command-line application and run it 9 | 10 | import ( 11 | "errors" 12 | "github.com/stretchr/testify/assert" 13 | "testing" 14 | ) 15 | 16 | type TestCommand struct { 17 | Cmd 18 | 19 | throwError string 20 | } 21 | 22 | func (c *TestCommand) DefineCommand(...CommandOptions) { 23 | options := CommandOptions{} 24 | options.Use = "test-cmd" 25 | options.Short = "A test command" 26 | options.FirstArgAlternativeForFlag = &AlternativeForFlagOptions{ 27 | Flag: "throw-error", 28 | Value: &c.throwError, 29 | } 30 | options.Run = func() { 31 | c.Output().InfofWithHints([]string{"This is a hint"}, "Some things to consider") 32 | 33 | if c.throwError == "throw-error" { 34 | c.CheckErr(errors.New("Expected error")) 35 | } 36 | } 37 | 38 | c.Cmd.DefineCommand(options) 39 | c.AddFlag(FlagOptions{Name: "throw-error", Usage: "Throw an error", String: &c.throwError}) 40 | } 41 | 42 | func TestTest(t *testing.T) { 43 | TestSetup(t) 44 | TestCmd[*TestCommand]() 45 | } 46 | 47 | func TestTest2(t *testing.T) { 48 | TestSetup(t) 49 | TestCmd[*TestCommand]("test-cmd") 50 | } 51 | 52 | func TestThrowError(t *testing.T) { 53 | TestSetup(t) 54 | 55 | assert.Panics(t, func() { 56 | TestCmd[*TestCommand]("throw-error") 57 | }) 58 | } 59 | 60 | func TestTest3(t *testing.T) { 61 | TestSetup(t) 62 | } 63 | -------------------------------------------------------------------------------- /internal/cmdparser/type.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package cmdparser 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/cmdparser/dependency" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | // Cmd is the main type used for defining and running command line programs. 12 | // It contains fields and methods for defining the command, setting its options, 13 | // and running the command. 14 | type Cmd struct { 15 | dependencies dependency.Options 16 | options CommandOptions 17 | command cobra.Command 18 | unitTesting bool 19 | } 20 | -------------------------------------------------------------------------------- /internal/config/endpoint_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TestEndpointExists(t *testing.T) { 12 | assert.Panics(t, func() { EndpointExists("") }) 13 | 14 | } 15 | -------------------------------------------------------------------------------- /internal/config/error.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | var errorCallback func(err error) 7 | 8 | func checkErr(err error) { 9 | errorCallback(err) 10 | } 11 | -------------------------------------------------------------------------------- /internal/config/error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "errors" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func Test_checkErr(t *testing.T) { 13 | assert.Panics(t, func() { 14 | checkErr(errors.New("verify error handler")) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /internal/config/initialize.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "fmt" 8 | "github.com/microsoft/go-sqlcmd/internal/net" 9 | "github.com/microsoft/go-sqlcmd/internal/secret" 10 | ) 11 | 12 | var encryptCallback func(plainText string, encryptionMethod string) (cipherText string) 13 | var decryptCallback func(cipherText string, encryptionMethod string) (secret string) 14 | var isLocalPortAvailableCallback func(port int) (portAvailable bool) 15 | 16 | // init sets up the package to work with a set of handlers to be used for the period 17 | // before the command-line has been parsed 18 | func init() { 19 | errorHandler := func(err error) { 20 | if err != nil { 21 | panic(err) 22 | } 23 | } 24 | traceHandler := func(format string, a ...any) { 25 | fmt.Printf(format, a...) 26 | } 27 | 28 | Initialize( 29 | errorHandler, 30 | traceHandler, 31 | secret.Encode, 32 | secret.Decode, 33 | net.IsLocalPortAvailable) 34 | } 35 | 36 | // Initialize sets the callback functions used by the config package. 37 | // These callback functions are used for logging errors, tracing debug messages, 38 | // encrypting and decrypting data, and checking if a local port is available. 39 | // The callback functions are passed to the function as arguments. 40 | // This function should be called at the start of the application to ensure that the 41 | // config package has the necessary callback functions available. 42 | func Initialize( 43 | errorHandler func(err error), 44 | traceHandler func(format string, a ...any), 45 | encryptHandler func(plainText string, encryptionMethod string) (cipherText string), 46 | decryptHandler func(cipherText string, encryptionMethod string) (secret string), 47 | isLocalPortAvailableHandler func(port int) (portAvailable bool), 48 | ) { 49 | errorCallback = errorHandler 50 | traceCallback = traceHandler 51 | encryptCallback = encryptHandler 52 | decryptCallback = decryptHandler 53 | isLocalPortAvailableCallback = isLocalPortAvailableHandler 54 | } 55 | -------------------------------------------------------------------------------- /internal/config/trace.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | var traceCallback func(format string, a ...any) 7 | 8 | func trace(format string, a ...any) { 9 | traceCallback(format, a...) 10 | } 11 | -------------------------------------------------------------------------------- /internal/config/user_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | . "github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func TestAddUser(t *testing.T) { 13 | assert.Panics(t, func() { 14 | AddUser(User{ 15 | Name: "", 16 | AuthenticationType: "basic", 17 | BasicAuth: nil, 18 | }) 19 | }) 20 | } 21 | 22 | func TestUserExists2(t *testing.T) { 23 | context := Context{ 24 | ContextDetails: ContextDetails{}, 25 | Name: "context", 26 | } 27 | assert.False(t, UserExists(context)) 28 | } 29 | 30 | func TestUserExists3(t *testing.T) { 31 | user := "user" 32 | context := Context{ 33 | ContextDetails: ContextDetails{User: &user}, 34 | Name: "context", 35 | } 36 | assert.True(t, UserExists(context)) 37 | } 38 | 39 | func TestNegAddUser(t *testing.T) { 40 | assert.Panics(t, func() { 41 | AddUser(User{ 42 | Name: "", 43 | AuthenticationType: "basic", 44 | BasicAuth: &BasicAuthDetails{ 45 | Username: "", 46 | PasswordEncryption: "none", 47 | Password: "", 48 | }, 49 | }) 50 | }) 51 | } 52 | 53 | func TestNegAddUser2(t *testing.T) { 54 | assert.Panics(t, func() { 55 | GetUser("doesnotexist") 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /internal/config/viper_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func Test_configureViper(t *testing.T) { 12 | assert.Panics(t, func() { 13 | configureViper("") 14 | }) 15 | } 16 | 17 | func Test_Load(t *testing.T) { 18 | SetFileNameForTest(t) 19 | Clean() 20 | Load() 21 | } 22 | 23 | func TestNeg_Load(t *testing.T) { 24 | filename = "" 25 | assert.Panics(t, func() { 26 | Load() 27 | }) 28 | } 29 | 30 | func TestNeg_Save(t *testing.T) { 31 | filename = "" 32 | assert.Panics(t, func() { 33 | Save() 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /internal/container/docker.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package container 5 | 6 | import ( 7 | "context" 8 | "github.com/docker/distribution/reference" 9 | "github.com/docker/distribution/registry/client" 10 | "net/http" 11 | ) 12 | 13 | // ListTags lists all tags for a container image located at a given 14 | // path in the container registry. It takes the path to the image and the 15 | // URL of the registry as input and returns a slice of strings containing 16 | // the tags. 17 | func ListTags(path string, baseURL string) []string { 18 | ctx := context.Background() 19 | repo, err := reference.WithName(path) 20 | checkErr(err) 21 | repository, err := client.NewRepository( 22 | repo, 23 | baseURL, 24 | http.DefaultTransport, 25 | ) 26 | checkErr(err) 27 | tagService := repository.Tags(ctx) 28 | tags, err := tagService.All(ctx) 29 | checkErr(err) 30 | 31 | return tags 32 | } 33 | -------------------------------------------------------------------------------- /internal/container/error.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package container 5 | 6 | var errorCallback func(err error) 7 | 8 | func checkErr(err error) { 9 | errorCallback(err) 10 | } 11 | -------------------------------------------------------------------------------- /internal/container/error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package container 5 | 6 | import ( 7 | "errors" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func Test_checkErr(t *testing.T) { 13 | assert.Panics(t, func() { 14 | checkErr(errors.New("verify error handler")) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /internal/container/initialize.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package container 5 | 6 | func init() { 7 | Initialize( 8 | func(err error) { 9 | if err != nil { 10 | panic(err) 11 | } 12 | }, 13 | func(format string, a ...any) {}) 14 | } 15 | 16 | func Initialize( 17 | errorHandler func(err error), 18 | traceHandler func(format string, a ...any)) { 19 | errorCallback = errorHandler 20 | traceCallback = traceHandler 21 | } 22 | -------------------------------------------------------------------------------- /internal/container/trace.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package container 5 | 6 | var traceCallback func(format string, a ...any) 7 | 8 | func trace(format string, a ...any) { 9 | traceCallback(format, a...) 10 | } 11 | -------------------------------------------------------------------------------- /internal/credman/credman_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package credman 5 | -------------------------------------------------------------------------------- /internal/credman/types_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package credman 5 | 6 | import ( 7 | syscall "golang.org/x/sys/windows" 8 | "time" 9 | ) 10 | 11 | const ( 12 | CredTypeGeneric CredentialType = 0x1 13 | ) 14 | 15 | // Credential is the basic credential structure. 16 | // A credential is identified by its target name. 17 | // The actual credential secret is available in the CredentialBlob field. 18 | type Credential struct { 19 | TargetName string 20 | Comment string 21 | LastWritten time.Time 22 | CredentialBlob []byte 23 | TargetAlias string 24 | UserName string 25 | Persist CredentialPersistence 26 | } 27 | 28 | // CredentialPersistence describes one of three persistence modes of a credential. 29 | // A detailed description of the available modes can be found on: 30 | // 31 | // https://learn.microsoft.com/windows/win32/api/wincred/ns-wincred-credentiala 32 | type CredentialPersistence uint32 33 | 34 | const ( 35 | // PersistSession indicates that the credential only persists for the life 36 | // of the current Windows login session. Such a credential is not visible in 37 | // any other logon session, even from the same user. 38 | PersistSession CredentialPersistence = 0x1 39 | ) 40 | 41 | // CredentialAttribute represents an application-specific attribute of a credential. 42 | type CredentialAttribute struct { 43 | Keyword string 44 | Value []byte 45 | } 46 | 47 | // Interface for syscall.Proc 48 | type proc interface { 49 | Call(a ...uintptr) (r1, r2 uintptr, lastErr error) 50 | } 51 | 52 | type CREDENTIAL struct { 53 | Flags uint32 54 | Type uint32 55 | TargetName *uint16 56 | Comment *uint16 57 | LastWritten syscall.Filetime 58 | CredentialBlobSize uint32 59 | CredentialBlob uintptr 60 | Persist uint32 61 | AttributeCount uint32 62 | Attributes uintptr 63 | TargetAlias *uint16 64 | UserName *uint16 65 | } 66 | 67 | type CREDENTIAL_ATTRIBUTE struct { 68 | Keyword *uint16 69 | Flags uint32 70 | ValueSize uint32 71 | Value uintptr 72 | } 73 | 74 | type CredentialType uint32 75 | -------------------------------------------------------------------------------- /internal/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package internal 5 | 6 | /* 7 | These internal packages abstract the following from the application (using 8 | dependency injection): 9 | 10 | - error handling (for non-control flow) 11 | - trace support (non-localized output) 12 | 13 | The above abstractions enable application code to not have to sprinkle 14 | if (err != nil) blocks (except when the application wants to affect application 15 | flow based on err) 16 | 17 | Do and Do Not: 18 | - Do verify parameter values and panic if these internal functions would be unable 19 | to succeed, to catch coding errors (do not panic for user input errors) 20 | - Do not output (except for in the `internal/output` package). Do use the injected 21 | trace method to output low level debugging information 22 | - Do not return error if client is not going use the error for control flow, call the 23 | injected checkErr instead, which will probably end up calling cobra.checkErr and exit: 24 | e.g. Do not sprinkle application (non-helper) code with: 25 | err, _ := fmt.printf("Hope this works") 26 | if (err != nil) { 27 | panic("How unlikely") 28 | } 29 | Do use the injected checkErr callback and let the application decide what to do 30 | err, _ := printf("Hope this works) 31 | checkErr(err) 32 | - Do not have an internal package take a dependency on another internal package 33 | unless they are building on each other, instead inject the needed capability in the 34 | internal.initiaize() 35 | e.g. Do not have the config package take a dependency on the secret package, instead 36 | inject the methods encrypt/decrypt to config in its initialize method, do not: 37 | 38 | package config 39 | 40 | import ( 41 | "github.com/microsoft/go-sqlcmd/cmd/internal/secret" 42 | ) 43 | 44 | Do instead: 45 | 46 | package config 47 | 48 | var encryptCallback func(plainText string) (cipherText string) 49 | var decryptCallback func(cipherText string) (secret string) 50 | 51 | func Initialize( 52 | encryptHandler func(plainText string) (cipherText string), 53 | decryptHandler func(cipherText string) (secret string), 54 | */ 55 | -------------------------------------------------------------------------------- /internal/http/error.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package http 5 | 6 | var errorCallback func(err error) 7 | 8 | func checkErr(err error) { 9 | errorCallback(err) 10 | } 11 | -------------------------------------------------------------------------------- /internal/http/error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package http 5 | 6 | import ( 7 | "errors" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func Test_checkErr(t *testing.T) { 13 | assert.Panics(t, func() { 14 | checkErr(errors.New("verify error handler")) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /internal/http/http.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package http 5 | 6 | import "net/http" 7 | 8 | func UrlExists(url string) (exists bool) { 9 | trace("http.Head to %q", url) 10 | resp, err := http.Head(url) 11 | if err != nil { 12 | trace("http.Head to %q failed with %v", url, err) 13 | return false 14 | } 15 | if resp.StatusCode != 200 { 16 | trace("http.Head to %q returned status code %d", url, resp.StatusCode) 17 | return false 18 | } 19 | 20 | trace("http.Head to %q succeeded", url) 21 | 22 | return true 23 | } 24 | -------------------------------------------------------------------------------- /internal/http/http_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package http 5 | 6 | import ( 7 | "github.com/stretchr/testify/assert" 8 | "net/http" 9 | "net/http/httptest" 10 | "testing" 11 | ) 12 | 13 | func TestUrlExists(t *testing.T) { 14 | // Test case 1: URL exists and returns a 200 status code 15 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 16 | w.WriteHeader(http.StatusOK) 17 | })) 18 | defer ts.Close() 19 | assert.True(t, UrlExists(ts.URL), "Expected UrlExists to return true for URL %q, but got false", ts.URL) 20 | 21 | // Test case 2: URL exists but returns a non-200 status code 22 | ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 23 | w.WriteHeader(http.StatusNotFound) 24 | })) 25 | defer ts.Close() 26 | assert.False(t, UrlExists(ts.URL), "Expected UrlExists to return false for URL %q, but got true", ts.URL) 27 | 28 | assert.False(t, UrlExists("http://invalid.url"), "Expected UrlExists to return false for invalid URL, but got true") 29 | } 30 | -------------------------------------------------------------------------------- /internal/http/initialize.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package http 5 | 6 | func init() { 7 | Initialize( 8 | func(err error) { 9 | if err != nil { 10 | panic(err) 11 | } 12 | }, 13 | func(format string, a ...any) {}) 14 | } 15 | 16 | func Initialize( 17 | errorHandler func(err error), 18 | traceHandler func(format string, a ...any)) { 19 | 20 | errorCallback = errorHandler 21 | traceCallback = traceHandler 22 | } 23 | -------------------------------------------------------------------------------- /internal/http/trace.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package http 5 | 6 | var traceCallback func(format string, a ...any) 7 | 8 | func trace(format string, a ...any) { 9 | traceCallback(format, a...) 10 | } 11 | -------------------------------------------------------------------------------- /internal/intialize.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package internal 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/config" 8 | "github.com/microsoft/go-sqlcmd/internal/container" 9 | "github.com/microsoft/go-sqlcmd/internal/http" 10 | "github.com/microsoft/go-sqlcmd/internal/io/file" 11 | "github.com/microsoft/go-sqlcmd/internal/net" 12 | "github.com/microsoft/go-sqlcmd/internal/pal" 13 | "github.com/microsoft/go-sqlcmd/internal/secret" 14 | "github.com/microsoft/go-sqlcmd/internal/sql" 15 | ) 16 | 17 | type InitializeOptions struct { 18 | ErrorHandler func(error) 19 | TraceHandler func(format string, a ...any) 20 | HintHandler func([]string) 21 | LineBreak string 22 | } 23 | 24 | // Initialize initializes various dependencies for the application with the provided options. 25 | // The dependencies that are initialized include file, sql, config, container, 26 | // secret, net, and pal. This function is typically called at the start of the application 27 | // to ensure that all dependencies are properly initialized before any other code is executed. 28 | func Initialize(options InitializeOptions) { 29 | if options.ErrorHandler == nil { 30 | panic("ErrorHandler is nil") 31 | } 32 | if options.TraceHandler == nil { 33 | panic("TraceHandler is nil") 34 | } 35 | if options.HintHandler == nil { 36 | panic("HintHandler is nil") 37 | } 38 | if options.LineBreak == "" { 39 | panic("LineBreak is empty") 40 | } 41 | file.Initialize(options.ErrorHandler, options.TraceHandler) 42 | sql.Initialize(options.ErrorHandler, options.TraceHandler, secret.Decode) 43 | config.Initialize(options.ErrorHandler, options.TraceHandler, secret.Encode, secret.Decode, net.IsLocalPortAvailable) 44 | container.Initialize(options.ErrorHandler, options.TraceHandler) 45 | secret.Initialize(options.ErrorHandler) 46 | net.Initialize(options.ErrorHandler, options.TraceHandler) 47 | http.Initialize(options.ErrorHandler, options.TraceHandler) 48 | pal.Initialize(options.ErrorHandler, options.LineBreak) 49 | } 50 | -------------------------------------------------------------------------------- /internal/intialize_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package internal 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/output" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func TestInitialize(t *testing.T) { 13 | o := output.New(output.Options{HintHandler: func(hints []string) { 14 | }, ErrorHandler: func(err error) {}}) 15 | options := InitializeOptions{ 16 | ErrorHandler: func(err error) { 17 | if err != nil { 18 | panic(err) 19 | } 20 | }, 21 | HintHandler: func(strings []string) {}, 22 | TraceHandler: o.Tracef, 23 | LineBreak: "\n", 24 | } 25 | Initialize(options) 26 | } 27 | 28 | func TestNegInitialize(t *testing.T) { 29 | options := InitializeOptions{ 30 | ErrorHandler: nil, 31 | } 32 | assert.Panics(t, func() { 33 | Initialize(options) 34 | }) 35 | } 36 | 37 | func TestNegInitialize2(t *testing.T) { 38 | options := InitializeOptions{ 39 | ErrorHandler: func(err error) {}, 40 | } 41 | assert.Panics(t, func() { 42 | Initialize(options) 43 | }) 44 | } 45 | 46 | func TestNegInitialize3(t *testing.T) { 47 | options := InitializeOptions{ 48 | ErrorHandler: func(err error) {}, 49 | TraceHandler: func(format string, a ...any) {}, 50 | } 51 | assert.Panics(t, func() { 52 | Initialize(options) 53 | }) 54 | } 55 | 56 | func TestNegInitialize4(t *testing.T) { 57 | options := InitializeOptions{ 58 | ErrorHandler: func(err error) {}, 59 | TraceHandler: func(format string, a ...any) {}, 60 | HintHandler: func(strings []string) {}, 61 | } 62 | assert.Panics(t, func() { 63 | Initialize(options) 64 | }) 65 | } 66 | 67 | func TestNegInitialize5(t *testing.T) { 68 | options := InitializeOptions{ 69 | ErrorHandler: func(err error) {}, 70 | TraceHandler: func(format string, a ...any) {}, 71 | HintHandler: func(strings []string) {}, 72 | LineBreak: "", 73 | } 74 | assert.Panics(t, func() { 75 | Initialize(options) 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /internal/io/file/error.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package file 5 | 6 | var errorCallback func(err error) 7 | 8 | func checkErr(err error) { 9 | errorCallback(err) 10 | } 11 | -------------------------------------------------------------------------------- /internal/io/file/error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package file 5 | 6 | import ( 7 | "errors" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func Test_checkErr(t *testing.T) { 13 | assert.Panics(t, func() { 14 | checkErr(errors.New("verify error handler")) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /internal/io/file/file.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package file 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/io/folder" 8 | "io/ioutil" 9 | "os" 10 | "path/filepath" 11 | ) 12 | 13 | func CloseFile(f *os.File) { 14 | err := f.Close() 15 | checkErr(err) 16 | } 17 | 18 | // CreateEmptyIfNotExists creates an empty file with the given filename if it 19 | // does not already exist. If the parent directory of the file does not exist, the 20 | // function will create it. The function is useful for ensuring that a file is 21 | // present before writing to it. 22 | func CreateEmptyIfNotExists(filename string) { 23 | if filename == "" { 24 | panic("filename must not be empty") 25 | } 26 | 27 | d, _ := filepath.Split(filename) 28 | if d != "" && !Exists(d) { 29 | trace("Folder %v does not exist, creating", d) 30 | folder.MkdirAll(d) 31 | } 32 | if !Exists(filename) { 33 | trace("File %v does not exist, creating empty 0 byte file", filename) 34 | handle, err := os.Create(filename) 35 | checkErr(err) 36 | defer func() { 37 | err := handle.Close() 38 | checkErr(err) 39 | }() 40 | } 41 | } 42 | 43 | // Exists checks if a file with the given filename exists in the file system. It 44 | // returns a boolean value indicating whether the file exists or not. 45 | func Exists(filename string) (exists bool) { 46 | if filename == "" { 47 | panic("filename must not be empty") 48 | } 49 | 50 | if _, err := os.Stat(filename); err == nil { 51 | exists = true 52 | } 53 | 54 | return 55 | } 56 | 57 | func GetContents(filename string) string { 58 | b, err := ioutil.ReadFile(filename) 59 | checkErr(err) 60 | 61 | return string(b) 62 | } 63 | 64 | func OpenFile(filename string) *os.File { 65 | f, err := os.Create(filename) 66 | checkErr(err) 67 | return f 68 | } 69 | 70 | // Remove is used to remove a file with the specified filename. The function 71 | // takes in the name of the file as an argument and deletes it from the file system. 72 | func Remove(filename string) { 73 | err := os.Remove(filename) 74 | checkErr(err) 75 | } 76 | 77 | func WriteString(f *os.File, s string) { 78 | _, err := f.WriteString(s) 79 | checkErr(err) 80 | } 81 | -------------------------------------------------------------------------------- /internal/io/file/initialize.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package file 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/io/folder" 8 | ) 9 | 10 | func init() { 11 | Initialize( 12 | func(err error) { 13 | if err != nil { 14 | panic(err) 15 | } 16 | }, 17 | func(format string, a ...any) {}) 18 | } 19 | 20 | func Initialize( 21 | errorHandler func(err error), 22 | traceHandler func(format string, a ...any)) { 23 | errorCallback = errorHandler 24 | traceCallback = traceHandler 25 | 26 | // this file helper depends on the folder helper (for example, to create folder paths 27 | // in passed in file names if the folders don't exist 28 | folder.Initialize(errorHandler, traceHandler) 29 | } 30 | -------------------------------------------------------------------------------- /internal/io/file/trace.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package file 5 | 6 | var traceCallback func(format string, a ...any) 7 | 8 | func trace(format string, a ...any) { 9 | traceCallback(format, a...) 10 | } 11 | -------------------------------------------------------------------------------- /internal/io/folder/error.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package folder 5 | 6 | var errorCallback func(err error) 7 | 8 | func checkErr(err error) { 9 | errorCallback(err) 10 | } 11 | -------------------------------------------------------------------------------- /internal/io/folder/error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package folder 5 | 6 | import ( 7 | "errors" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func Test_checkErr(t *testing.T) { 13 | assert.Panics(t, func() { 14 | checkErr(errors.New("verify error handler")) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /internal/io/folder/folder.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package folder 5 | 6 | import ( 7 | "os" 8 | ) 9 | 10 | // Getwd returns the current working directory 11 | func Getwd() string { 12 | path, err := os.Getwd() 13 | checkErr(err) 14 | return path 15 | } 16 | 17 | // MkdirAll creates a directory with the given name if it does not already exist. 18 | func MkdirAll(folder string) { 19 | if folder == "" { 20 | panic("folder must not be empty") 21 | } 22 | if _, err := os.Stat(folder); os.IsNotExist(err) { 23 | trace("Folder %v does not exist, creating", folder) 24 | err := os.MkdirAll(folder, os.ModePerm) 25 | checkErr(err) 26 | } 27 | } 28 | 29 | // RemoveAll removes a folder and all of its contents at the given path. 30 | func RemoveAll(folder string) { 31 | err := os.RemoveAll(folder) 32 | checkErr(err) 33 | } 34 | -------------------------------------------------------------------------------- /internal/io/folder/folder_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package folder 5 | 6 | import ( 7 | "github.com/stretchr/testify/assert" 8 | "os" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func TestMkdirAll(t *testing.T) { 14 | folderName := "test-folder" 15 | type args struct { 16 | folder string 17 | } 18 | tests := []struct { 19 | name string 20 | args args 21 | }{ 22 | {name: "default", args: args{folder: folderName}}, 23 | {name: "noFolderNamePanic", args: args{folder: ""}}, 24 | } 25 | 26 | cleanup(folderName) 27 | defer cleanup(folderName) 28 | 29 | for _, tt := range tests { 30 | t.Run(tt.name, func(t *testing.T) { 31 | 32 | // If test name ends in 'Panic' expect a Panic 33 | if strings.HasSuffix(tt.name, "Panic") { 34 | assert.Panics(t, func() { 35 | MkdirAll(tt.args.folder) 36 | }) 37 | } else { 38 | MkdirAll(tt.args.folder) 39 | } 40 | 41 | }) 42 | } 43 | } 44 | 45 | func TestGetwd(t *testing.T) { 46 | // Test 1: Check that the function returns the correct path 47 | wd, err := os.Getwd() 48 | assert.NoErrorf(t, err, "unexpected error: %v", err) 49 | assert.Equal(t, Getwd(), wd) 50 | } 51 | 52 | func cleanup(folderName string) { 53 | RemoveAll(folderName) 54 | } 55 | -------------------------------------------------------------------------------- /internal/io/folder/initialize.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package folder 5 | 6 | func init() { 7 | Initialize( 8 | func(err error) { 9 | if err != nil { 10 | panic(err) 11 | } 12 | }, 13 | func(format string, a ...any) {}) 14 | } 15 | 16 | func Initialize( 17 | errorHandler func(err error), 18 | traceHandler func(format string, a ...any)) { 19 | 20 | errorCallback = errorHandler 21 | traceCallback = traceHandler 22 | } 23 | -------------------------------------------------------------------------------- /internal/io/folder/trace.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package folder 5 | 6 | var traceCallback func(format string, a ...any) 7 | 8 | func trace(format string, a ...any) { 9 | traceCallback(format, a...) 10 | } 11 | -------------------------------------------------------------------------------- /internal/localizer/constants.go: -------------------------------------------------------------------------------- 1 | package localizer 2 | 3 | const ( 4 | UseContextCommand = "sqlcmd config use-context mssql" 5 | PasswordEnvVar = "SQLCMD_PASSWORD" 6 | PasswordEnvVar2 = "SQLCMDPASSWORD" 7 | EndpointFlag = "--endpoint" 8 | FeedbackUrl = "https://aka.ms/sqlcmd-feedback" 9 | PasswordEncryptFlag = "--password-encryption" 10 | AuthTypeFlag = "--auth-type" 11 | ModernAuthTypeBasic = "basic" 12 | ModernAuthTypeOther = "other" 13 | UserNameFlag = "--username" 14 | NameFlag = "--name" 15 | GetContextCommand = "sqlcmd config get-contexts" 16 | GetEndpointsCommand = "sqlcmd config get-endpoints" 17 | GetUsersCommand = "sqlcmd config get-users" 18 | RunQueryExample = "sqlcmd query \"SELECT @@SERVERNAME\"" 19 | UninstallCommand = "sqlcmd uninstall" 20 | AcceptEulaFlag = "--accept-eula" 21 | AcceptEulaEnvVar = "SQLCMD_ACCEPT_EULA" 22 | PodmanPsCommand = "podman ps" 23 | DockerPsCommand = "docker ps" 24 | HelpFlag = "--help" 25 | QueryAndExitFlag = "-Q" 26 | QueryFlag = "-q" 27 | DbNameVar = "SQLCMDDBNAME" 28 | BatchTerminatorGo = "GO" 29 | ConnStrPattern = "[[tcp:]|[lpc:]|[np:]]server[\\instance_name][,port]" 30 | ServerEnvVar = "SQLCMDSERVER" 31 | InsertKeyword = "INSERT" 32 | PacketSizeVar = "SQLCMDPACKETSIZE" 33 | LoginTimeOutVar = "SQLCMDLOGINTIMEOUT" 34 | WorkstationVar = "SQLCMDWORKSTATION" 35 | ApplicationIntentFlagShort = "-K" 36 | DosErrorLevel = "DOS ERRORLEVEL" 37 | StdoutName = "stdout" 38 | ColSeparatorVar = "SQLCMDCOLSEP" 39 | ErrorLevel = "ERRORLEVEL" 40 | AppIntentValues = `"readonly"` 41 | FormatValues = "\"horiz\",\"horizontal\",\"vert\",\"vertical\"" 42 | ErrToStderrValues = "\"-1\",\"0\",\"1\"" 43 | ) 44 | -------------------------------------------------------------------------------- /internal/localizer/localizer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package localizer 5 | 6 | import ( 7 | // Import the internal/translations so that its init() function 8 | // is run. It's really important that we do this here so that the 9 | // default message catalog is updated to use our translations 10 | // *before* we initialize the message.Printer instances below. 11 | 12 | "fmt" 13 | "os" 14 | "strings" 15 | 16 | _ "github.com/microsoft/go-sqlcmd/internal/translations" 17 | "golang.org/x/text/language" 18 | "golang.org/x/text/message" 19 | ) 20 | 21 | var Translator *message.Printer 22 | 23 | var supportedLanguages = map[string]string{ 24 | "de-de": "de-DE", 25 | "fr-fr": "fr-FR", 26 | "en-us": "en-US", 27 | "zh-hans": "zh-CN", 28 | "zh-cn": "zh-CN", 29 | "zh-hant": "zh-TW", 30 | "zh-tw": "zh-TW", 31 | "it-it": "it-IT", 32 | "ja-jp": "ja-JP", 33 | "ko-kr": "ko-KR", 34 | "pt-br": "pt-BR", 35 | "ru-ru": "ru-RU", 36 | "es-es": "es-ES", 37 | } 38 | 39 | // init() initializes the language automatically 40 | // based on env var SQLCMD_LANG which expects language 41 | // tag such as en-us, de-de, fr-ch, etc. 42 | func init() { 43 | localeName := strings.ToLower(os.Getenv("SQLCMD_LANG")) 44 | if _, ok := supportedLanguages[localeName]; !ok { 45 | localeName = "en-us" 46 | } 47 | Translator = message.NewPrinter(language.MustParse(supportedLanguages[localeName])) 48 | } 49 | 50 | // Errorf() is wrapper function to create localized errors 51 | func Errorf(format string, a ...any) error { 52 | errMsg := Translator.Sprintf(format, a...) 53 | return fmt.Errorf(errMsg) 54 | } 55 | 56 | // Sprintf() is wrapper function to create localized string 57 | func Sprintf(key message.Reference, args ...interface{}) string { 58 | return Translator.Sprintf(key, args...) 59 | } 60 | 61 | // ProductBanner() returns the localized product banner string 62 | func ProductBanner() string { 63 | return Sprintf("sqlcmd: Install/Create/Query SQL Server, Azure SQL, and Tools") 64 | } 65 | -------------------------------------------------------------------------------- /internal/localizer/localizer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package localizer 5 | 6 | import ( 7 | "os" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | // TestErrorfMissingLanguage tests that Errorf returns the correct error message when the language is not found 14 | func TestErrorfMissingLanguage(t *testing.T) { 15 | os.Setenv("SQLCMD_LANG", "xx-xx") 16 | err := Errorf("This is a %s error", "test") 17 | assert.EqualError(t, err, "This is a test error") 18 | } 19 | 20 | // TestSprintfPanic tests that Sprintf panics when the language is not found 21 | func TestSprintfPanic(t *testing.T) { 22 | os.Setenv("SQLCMD_LANG", "") 23 | assert.Panics(t, func() { Sprintf(nil, "Missing key") }) 24 | } 25 | -------------------------------------------------------------------------------- /internal/net/error.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package net 5 | 6 | var errorCallback func(err error) 7 | 8 | func checkErr(err error) { 9 | errorCallback(err) 10 | } 11 | -------------------------------------------------------------------------------- /internal/net/error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package net 5 | 6 | import ( 7 | "errors" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func Test_checkErr(t *testing.T) { 13 | assert.Panics(t, func() { 14 | checkErr(errors.New("verify error handler")) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /internal/net/initialize.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package net 5 | 6 | func init() { 7 | Initialize( 8 | func(err error) { 9 | if err != nil { 10 | panic(err) 11 | } 12 | }, 13 | func(format string, a ...any) {}) 14 | } 15 | 16 | func Initialize( 17 | errorHandler func(err error), 18 | traceHandler func(format string, a ...any)) { 19 | 20 | errorCallback = errorHandler 21 | traceCallback = traceHandler 22 | } 23 | -------------------------------------------------------------------------------- /internal/net/net.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package net 5 | 6 | import ( 7 | "net" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | // IsLocalPortAvailable takes a port number and returns a boolean indicating 13 | // whether the port is available for use. 14 | func IsLocalPortAvailable(port int) (portAvailable bool) { 15 | timeout := time.Second 16 | 17 | hostPort := net.JoinHostPort("localhost", strconv.Itoa(port)) 18 | trace( 19 | "Checking if local port %#v is available using DialTimeout(tcp, %v, timeout: %d)", 20 | port, 21 | hostPort, 22 | timeout, 23 | ) 24 | conn, err := net.DialTimeout( 25 | "tcp", 26 | hostPort, 27 | timeout, 28 | ) 29 | if err != nil { 30 | trace( 31 | "Expected connecting error '%v' on local port %#v, therefore port is available)", 32 | err, 33 | port, 34 | ) 35 | portAvailable = true 36 | } 37 | if conn != nil { 38 | err := conn.Close() 39 | checkErr(err) 40 | trace("Local port '%#v' is not available", port) 41 | } else { 42 | trace("Local port '%#v' is available", port) 43 | } 44 | 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /internal/net/net_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package net 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | // TestIsLocalPortAvailable verified the function for both available and unavailable 11 | // code (this function expects a local SQL Server instance listening on port 1433 12 | func TestIsLocalPortAvailable(t *testing.T) { 13 | var testedPortAvailable bool 14 | var testedNotPortAvailable bool 15 | 16 | for i := 1432; i <= 1434; i++ { 17 | isPortAvailable := IsLocalPortAvailable(i) 18 | if isPortAvailable { 19 | testedPortAvailable = true 20 | t.Logf("Port %#v is available", i) 21 | } else { 22 | testedNotPortAvailable = true 23 | t.Logf("Port %#v is not available", i) 24 | } 25 | if testedPortAvailable && testedNotPortAvailable { 26 | return 27 | } 28 | } 29 | 30 | t.Log("Didn't find both an available port and unavailable port") 31 | t.Fail() 32 | 33 | } 34 | -------------------------------------------------------------------------------- /internal/net/trace.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package net 5 | 6 | var traceCallback func(format string, a ...any) 7 | 8 | func trace(format string, a ...any) { 9 | traceCallback(format, a...) 10 | } 11 | -------------------------------------------------------------------------------- /internal/output/factory.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package output 5 | 6 | import ( 7 | "fmt" 8 | "github.com/microsoft/go-sqlcmd/internal/output/formatter" 9 | "github.com/microsoft/go-sqlcmd/internal/test" 10 | "os" 11 | ) 12 | 13 | // New initializes a new Output instance with the specified options. If options 14 | // are not provided, default values are used. The function sets the error callback 15 | // and the hint callback based on the value of the unitTesting field in the 16 | // provided options. If unitTesting is true, the error callback is set to 17 | // panic on error, otherwise it is set to use cobra.CheckErr to handle errors. 18 | func New(options Options) *Output { 19 | if options.LoggingLevel == 0 { 20 | options.LoggingLevel = 2 21 | } 22 | if options.StandardWriter == nil { 23 | options.StandardWriter = os.Stdout 24 | } 25 | if options.ErrorHandler == nil { 26 | if test.IsRunningInTestExecutor() && !options.unitTesting { 27 | options.ErrorHandler = func(err error) { 28 | if err != nil { 29 | panic(err) 30 | } 31 | } 32 | } else { 33 | panic("Must provide Error Handler (the process (" + os.Args[0] + ") host is not a test executor)") 34 | } 35 | 36 | } 37 | if options.HintHandler == nil { 38 | if test.IsRunningInTestExecutor() && !options.unitTesting { 39 | options.HintHandler = func(hints []string) { 40 | fmt.Println(hints) 41 | } 42 | } else { 43 | panic("Must provide hint handler (the process " + os.Args[0] + " host is not a test executor)") 44 | } 45 | } 46 | 47 | f := formatter.New(formatter.Options{ 48 | SerializationFormat: options.OutputType, 49 | StandardOutput: options.StandardWriter, 50 | ErrorHandler: options.ErrorHandler, 51 | }) 52 | 53 | return &Output{ 54 | formatter: f, 55 | loggingLevel: options.LoggingLevel, 56 | standardWriteCloser: options.StandardWriter, 57 | errorCallback: options.ErrorHandler, 58 | hintCallback: options.HintHandler, 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /internal/output/factory_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package output 5 | 6 | import ( 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TestFactory(t *testing.T) { 12 | o := New(Options{unitTesting: false, HintHandler: func(hints []string) { 13 | 14 | }, ErrorHandler: func(err error) { 15 | 16 | }}) 17 | o.errorCallback(nil) 18 | } 19 | 20 | func TestNegtactory(t *testing.T) { 21 | assert.Panics(t, func() { 22 | New(Options{unitTesting: true, 23 | HintHandler: func(hints []string) {}, 24 | ErrorHandler: nil}) 25 | }) 26 | } 27 | 28 | func TestNegFactory2(t *testing.T) { 29 | assert.Panics(t, func() { 30 | New(Options{unitTesting: true, 31 | HintHandler: nil, 32 | ErrorHandler: func(err error) {}}) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /internal/output/formatter/base.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package formatter 5 | 6 | import "io" 7 | 8 | type Base struct { 9 | StandardOutput io.WriteCloser 10 | ErrorHandlerCallback func(err error) 11 | } 12 | 13 | func (f *Base) CheckErr(err error) { 14 | if f.ErrorHandlerCallback == nil { 15 | panic("errorHandlerCallback not initialized") 16 | } 17 | 18 | f.ErrorHandlerCallback(err) 19 | } 20 | 21 | func (f *Base) Output(bytes []byte) { 22 | _, err := f.StandardOutput.Write(bytes) 23 | f.CheckErr(err) 24 | } 25 | -------------------------------------------------------------------------------- /internal/output/formatter/base_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package formatter 5 | 6 | import ( 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TestBase_CheckErr(t *testing.T) { 12 | f := &Base{ErrorHandlerCallback: nil} 13 | 14 | assert.Panics(t, func() { 15 | f.CheckErr(nil) 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /internal/output/formatter/factory.go: -------------------------------------------------------------------------------- 1 | package formatter 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | // New creates a new instance of the Formatter interface. It takes an Options 9 | // struct as input and sets default values for some of the fields if they are 10 | // not specified. The SerializationFormat field of the Options struct is used 11 | // to determine which implementation of the Formatter interface to return. 12 | // If the specified format is not supported, the function will panic. 13 | func New(options Options, 14 | ) (f Formatter) { 15 | if options.SerializationFormat == "" { 16 | options.SerializationFormat = "yaml" 17 | } 18 | if options.ErrorHandler == nil { 19 | options.ErrorHandler = func(err error) {} 20 | } 21 | if options.StandardOutput == nil { 22 | options.StandardOutput = os.Stdout 23 | } 24 | 25 | switch options.SerializationFormat { 26 | case "json": 27 | f = &Json{Base: Base{ 28 | StandardOutput: options.StandardOutput, 29 | ErrorHandlerCallback: options.ErrorHandler}} 30 | case "yaml": 31 | f = &Yaml{Base: Base{ 32 | StandardOutput: options.StandardOutput, 33 | ErrorHandlerCallback: options.ErrorHandler}} 34 | case "xml": 35 | f = &Xml{Base: Base{ 36 | StandardOutput: options.StandardOutput, 37 | ErrorHandlerCallback: options.ErrorHandler}} 38 | default: 39 | panic(fmt.Sprintf("Format '%v' not supported", options.SerializationFormat)) 40 | } 41 | 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /internal/output/formatter/factory_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package formatter 5 | 6 | import ( 7 | "github.com/stretchr/testify/assert" 8 | "log" 9 | "testing" 10 | ) 11 | 12 | func TestFormatter(t *testing.T) { 13 | s := []string{"serialize this"} 14 | 15 | var f Formatter 16 | f = New(Options{SerializationFormat: "yaml"}) 17 | f.Serialize(s) 18 | f = New(Options{SerializationFormat: "xml"}) 19 | f.Serialize(s) 20 | f = New(Options{SerializationFormat: "json"}) 21 | f.Serialize(s) 22 | 23 | log.Println("This is here to ensure a newline is in test output") 24 | } 25 | 26 | func TestNegFormatterBadFormat(t *testing.T) { 27 | assert.Panics(t, func() { 28 | New(Options{SerializationFormat: "badbad"}) 29 | }) 30 | } 31 | 32 | func TestFormatterEmptyFormat(t *testing.T) { 33 | s := "serialize this" 34 | f := New(Options{SerializationFormat: ""}) 35 | f.Serialize(s) 36 | } 37 | -------------------------------------------------------------------------------- /internal/output/formatter/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package formatter 5 | 6 | // Formatter defines a formatter for serializing an input object into a byte slice. 7 | // The Serialize method serializes the input object and returns the resulting 8 | // byte slice. The CheckErr method handles any error encountered during 9 | // the serialization process. 10 | type Formatter interface { 11 | Serialize(in interface{}) (bytes []byte) 12 | CheckErr(err error) 13 | } 14 | -------------------------------------------------------------------------------- /internal/output/formatter/json.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package formatter 5 | 6 | import ( 7 | "encoding/json" 8 | ) 9 | 10 | type Json struct { 11 | Base 12 | } 13 | 14 | func (f *Json) Serialize(in interface{}) (bytes []byte) { 15 | var err error 16 | 17 | bytes, err = json.MarshalIndent(in, "", " ") 18 | f.Base.CheckErr(err) 19 | f.Base.Output(bytes) 20 | 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /internal/output/formatter/options.go: -------------------------------------------------------------------------------- 1 | package formatter 2 | 3 | import "io" 4 | 5 | // Options defines the options for creating a new Formatter instance. 6 | type Options struct { 7 | SerializationFormat string 8 | StandardOutput io.WriteCloser 9 | ErrorHandler func(err error) 10 | } 11 | -------------------------------------------------------------------------------- /internal/output/formatter/xml.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package formatter 5 | 6 | import ( 7 | "encoding/xml" 8 | ) 9 | 10 | type Xml struct { 11 | Base 12 | } 13 | 14 | func (f *Xml) Serialize(in interface{}) (bytes []byte) { 15 | var err error 16 | 17 | bytes, err = xml.MarshalIndent(in, "", " ") 18 | f.Base.CheckErr(err) 19 | f.Base.Output(bytes) 20 | 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /internal/output/formatter/yaml.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package formatter 5 | 6 | import ( 7 | "gopkg.in/yaml.v2" 8 | ) 9 | 10 | type Yaml struct { 11 | Base 12 | } 13 | 14 | func (f *Yaml) Serialize(in interface{}) (bytes []byte) { 15 | var err error 16 | 17 | bytes, err = yaml.Marshal(in) 18 | f.Base.CheckErr(err) 19 | f.Base.Output(bytes) 20 | 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /internal/output/options.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "github.com/microsoft/go-sqlcmd/internal/output/verbosity" 5 | "io" 6 | ) 7 | 8 | type Options struct { 9 | OutputType string 10 | LoggingLevel verbosity.Level 11 | StandardWriter io.WriteCloser 12 | 13 | ErrorHandler func(err error) 14 | HintHandler func(hints []string) 15 | 16 | unitTesting bool 17 | } 18 | -------------------------------------------------------------------------------- /internal/output/type.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package output 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/output/formatter" 8 | "github.com/microsoft/go-sqlcmd/internal/output/verbosity" 9 | "io" 10 | ) 11 | 12 | type Output struct { 13 | errorCallback func(err error) 14 | hintCallback func(hints []string) 15 | 16 | formatter formatter.Formatter 17 | loggingLevel verbosity.Level 18 | standardWriteCloser io.WriteCloser 19 | } 20 | -------------------------------------------------------------------------------- /internal/output/verbosity/level.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package verbosity 5 | 6 | // Level is an enumeration representing different verbosity levels for logging, 7 | // ranging from Error to Trace. The values of the enumeration are 8 | // Error, Warn, Info, Debug, and Trace, in increasing order of verbosity. 9 | type Level int 10 | 11 | const ( 12 | Error Level = iota 13 | Warn 14 | Info 15 | Debug 16 | Trace 17 | ) 18 | -------------------------------------------------------------------------------- /internal/pal/error.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package pal 5 | 6 | var errorCallback func(err error) 7 | 8 | func checkErr(err error) { 9 | errorCallback(err) 10 | } 11 | -------------------------------------------------------------------------------- /internal/pal/intialize.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package pal 5 | 6 | func init() { 7 | Initialize(func(err error) { 8 | if err != nil { 9 | panic(err) 10 | } 11 | }, defaultLineBreak()) 12 | } 13 | 14 | func Initialize(handler func(err error), endOfLine string) { 15 | errorCallback = handler 16 | lineBreak = endOfLine 17 | } 18 | -------------------------------------------------------------------------------- /internal/pal/pal.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | // Package pal provides functions that need to operate differently across different 5 | // operating systems and/or platforms. 6 | package pal 7 | 8 | import ( 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | var lineBreak string 15 | 16 | // FilenameInUserHomeDotDirectory returns the full path and filename 17 | // to the filename in the dotDirectory (e.g. .sqlcmd) in the user's home directory 18 | // e.g. c:\users\username 19 | func FilenameInUserHomeDotDirectory(dotDirectory string, filename string) string { 20 | home, err := os.UserHomeDir() 21 | checkErr(err) 22 | 23 | return filepath.Join(home, dotDirectory, filename) 24 | } 25 | 26 | // UserName returns the name of the currently logged-in user 27 | func UserName() (userName string) { 28 | return username() 29 | } 30 | 31 | // CmdLineWithEnvVars generates a command-line that can be run at the 32 | // operating system command-line (e.g. bash or cmd) which also requires 33 | // one or more environment variables to also be set 34 | func CmdLineWithEnvVars(vars []string, cmd string) string { 35 | var sb strings.Builder 36 | for _, v := range vars { 37 | sb.WriteString(CreateEnvVarKeyword()) 38 | sb.WriteString(" " + cliQuoteIdentifier() + v + cliQuoteIdentifier()) 39 | } 40 | sb.WriteString(cliCommandSeparator()) 41 | sb.WriteString(cmd) 42 | 43 | return sb.String() 44 | } 45 | 46 | func LineBreak() string { 47 | if lineBreak == "" { 48 | panic("Initialize has not been called") 49 | } 50 | 51 | return lineBreak 52 | } 53 | -------------------------------------------------------------------------------- /internal/pal/pal_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package pal 5 | 6 | import "os" 7 | 8 | func CreateEnvVarKeyword() string { 9 | return "export" 10 | } 11 | 12 | func cliQuoteIdentifier() string { 13 | return `'` 14 | } 15 | 16 | func cliCommandSeparator() string { 17 | return `; ` 18 | } 19 | 20 | func defaultLineBreak() string { 21 | return "\r\n" 22 | } 23 | 24 | func username() string { 25 | return os.Getenv("USER") 26 | } 27 | -------------------------------------------------------------------------------- /internal/pal/pal_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package pal 5 | 6 | import "os" 7 | 8 | func CreateEnvVarKeyword() string { 9 | return "export" 10 | } 11 | 12 | func cliQuoteIdentifier() string { 13 | return `'` 14 | } 15 | 16 | func cliCommandSeparator() string { 17 | return `; ` 18 | } 19 | 20 | func defaultLineBreak() string { 21 | return "\n" 22 | } 23 | 24 | func username() string { 25 | return os.Getenv("USER") 26 | } 27 | -------------------------------------------------------------------------------- /internal/pal/pal_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package pal 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "github.com/stretchr/testify/assert" 10 | "testing" 11 | ) 12 | 13 | func TestFilenameInUserHomeDotDirectory(t *testing.T) { 14 | FilenameInUserHomeDotDirectory(".foo", "bar") 15 | } 16 | 17 | func TestLineBreak(t *testing.T) { 18 | LineBreak() 19 | } 20 | 21 | func TestNegLineBreak(t *testing.T) { 22 | lineBreak = "" 23 | assert.Panics(t, func() { 24 | LineBreak() 25 | }) 26 | } 27 | 28 | func TestCheckErr(t *testing.T) { 29 | assert.Panics(t, func() { 30 | checkErr(errors.New("test")) 31 | }) 32 | } 33 | 34 | func TestUserName(t *testing.T) { 35 | user := UserName() 36 | fmt.Println(user) 37 | } 38 | 39 | func TestCmdLineWithEnvVars(t *testing.T) { 40 | cmdLine := CmdLineWithEnvVars([]string{"ENVVAR=FOOBAR"}, "cmd-to-run.exe") 41 | fmt.Println(cmdLine) 42 | } 43 | -------------------------------------------------------------------------------- /internal/pal/pal_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package pal 5 | 6 | import "os" 7 | 8 | func CreateEnvVarKeyword() string { 9 | return "SET" 10 | } 11 | 12 | func cliQuoteIdentifier() string { 13 | return `"` 14 | } 15 | 16 | func cliCommandSeparator() string { 17 | return ` & ` 18 | } 19 | 20 | func defaultLineBreak() string { 21 | return "\n" 22 | } 23 | 24 | func username() string { 25 | return os.Getenv("USERNAME") 26 | } 27 | -------------------------------------------------------------------------------- /internal/secret/encryption_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package secret 5 | 6 | var encryptionMethods = []string{"none"} 7 | 8 | func encrypt(plainText string) (cipherText string) { 9 | 10 | //BUG(stuartpa): Encryption not yet implemented on macOS, will use the KeyChain 11 | cipherText = plainText 12 | 13 | return 14 | } 15 | 16 | func decrypt(cipherText string) (secret string) { 17 | secret = cipherText 18 | 19 | //BUG(stuartpa): Encryption not yet implemented on macOS, will use the KeyChain 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /internal/secret/encryption_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package secret 5 | 6 | var encryptionMethods = []string{"none"} 7 | 8 | func encrypt(plainText string) (cipherText string) { 9 | 10 | //BUG(stuartpa): Encryption not yet implemented on linux 11 | cipherText = plainText 12 | 13 | return 14 | } 15 | 16 | func decrypt(cipherText string) (secret string) { 17 | secret = cipherText 18 | 19 | //BUG(stuartpa): Encryption not yet implemented on linux 20 | 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /internal/secret/encryption_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package secret 5 | 6 | import ( 7 | "github.com/billgraziano/dpapi" 8 | ) 9 | 10 | var encryptionMethods = []string{"none", "dpapi"} 11 | 12 | func encrypt(plainText string) (cipherText string) { 13 | var err error 14 | 15 | cipherText, err = dpapi.Encrypt(plainText) 16 | checkErr(err) 17 | 18 | return 19 | } 20 | 21 | func decrypt(cipherText string) (secret string) { 22 | var err error 23 | 24 | secret, err = dpapi.Decrypt(cipherText) 25 | checkErr(err) 26 | 27 | return 28 | } 29 | -------------------------------------------------------------------------------- /internal/secret/error.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package secret 5 | 6 | var errorCallback func(err error) 7 | 8 | func checkErr(err error) { 9 | errorCallback(err) 10 | } 11 | -------------------------------------------------------------------------------- /internal/secret/error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package secret 5 | 6 | import ( 7 | "errors" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func Test_checkErr(t *testing.T) { 13 | assert.Panics(t, func() { 14 | checkErr(errors.New("verify error handler")) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /internal/secret/generate.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package secret 5 | 6 | import ( 7 | "crypto/rand" 8 | "math/big" 9 | mathRand "math/rand" 10 | "strings" 11 | ) 12 | 13 | const ( 14 | lowerCharSet = "abcdedfghijklmnopqrstuvwxyz" 15 | upperCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 16 | numberSet = "0123456789" 17 | ) 18 | 19 | // Generate generates a random password of a specified length. The password 20 | // will contain at least the specified number of special characters, 21 | // numeric digits, and upper-case letters. The remaining characters in the 22 | // password will be selected from a combination of lower-case letters, special 23 | // characters, and numeric digits. The special characters are chosen from 24 | // the provided special character set. The generated password is returned 25 | // as a string. 26 | func Generate(passwordLength, minSpecialChar, minNum, minUpperCase int, specialCharSet string) string { 27 | var password strings.Builder 28 | allCharSet := lowerCharSet + upperCharSet + specialCharSet + numberSet 29 | 30 | //Set special character 31 | for i := 0; i < minSpecialChar; i++ { 32 | idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(specialCharSet)))) 33 | checkErr(err) 34 | _, err = password.WriteString(string(specialCharSet[idx.Int64()])) 35 | checkErr(err) 36 | } 37 | 38 | //Set numeric 39 | for i := 0; i < minNum; i++ { 40 | idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(numberSet)))) 41 | checkErr(err) 42 | _, err = password.WriteString(string(numberSet[idx.Int64()])) 43 | checkErr(err) 44 | } 45 | 46 | //Set uppercase 47 | for i := 0; i < minUpperCase; i++ { 48 | idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(upperCharSet)))) 49 | checkErr(err) 50 | _, err = password.WriteString(string(upperCharSet[idx.Int64()])) 51 | checkErr(err) 52 | } 53 | 54 | remainingLength := passwordLength - minSpecialChar - minNum - minUpperCase 55 | for i := 0; i < remainingLength; i++ { 56 | idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(allCharSet)))) 57 | checkErr(err) 58 | _, err = password.WriteString(string(allCharSet[idx.Int64()])) 59 | checkErr(err) 60 | } 61 | 62 | inRune := []rune(password.String()) 63 | mathRand.Shuffle(len(inRune), func(i, j int) { 64 | inRune[i], inRune[j] = inRune[j], inRune[i] 65 | }) 66 | return string(inRune) 67 | } 68 | -------------------------------------------------------------------------------- /internal/secret/generate_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package secret 5 | 6 | import ( 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TestGenerate(t *testing.T) { 12 | type args struct { 13 | passwordLength int 14 | minSpecialChar int 15 | minNum int 16 | minUpperCase int 17 | specialChars string 18 | } 19 | tests := []struct { 20 | name string 21 | args args 22 | want string 23 | }{ 24 | { 25 | name: "positiveSimple", 26 | args: args{ 27 | passwordLength: 50, 28 | minSpecialChar: 10, 29 | minNum: 10, 30 | minUpperCase: 10, 31 | specialChars: "!@#$%&*", 32 | }, 33 | want: "", 34 | }, 35 | } 36 | for _, tt := range tests { 37 | t.Run(tt.name, func(t *testing.T) { 38 | got := Generate( 39 | tt.args.passwordLength, 40 | tt.args.minSpecialChar, 41 | tt.args.minNum, 42 | tt.args.minUpperCase, 43 | tt.args.specialChars, 44 | ) 45 | assert.Len(t, got, tt.args.passwordLength) 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /internal/secret/initialize.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package secret 5 | 6 | func init() { 7 | Initialize(func(err error) { 8 | if err != nil { 9 | panic(err) 10 | } 11 | }) 12 | } 13 | 14 | func Initialize(handler func(err error)) { 15 | errorCallback = handler 16 | } 17 | -------------------------------------------------------------------------------- /internal/secret/secret_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package secret 5 | 6 | import ( 7 | "github.com/stretchr/testify/assert" 8 | "runtime" 9 | "testing" 10 | ) 11 | 12 | func TestEncodeAndDecode(t *testing.T) { 13 | notEncrypted := Encode("plainText", "none") 14 | plainText := Decode(notEncrypted, "none") 15 | assert.Equal(t, "plainText", plainText) 16 | 17 | if runtime.GOOS == "windows" { 18 | encrypted := Encode("plainText", "dpapi") 19 | plainText := Decode(encrypted, "dpapi") 20 | assert.Equal(t, "plainText", plainText) 21 | } 22 | } 23 | 24 | func TestNegEncode(t *testing.T) { 25 | assert.Panics(t, func() { 26 | Encode("", "none") 27 | }) 28 | } 29 | 30 | func TestNegEncodeBadEncryptionMethod(t *testing.T) { 31 | assert.Panics(t, func() { 32 | Encode("plainText", "does-not-exist") 33 | }) 34 | } 35 | 36 | func TestNegDecode(t *testing.T) { 37 | assert.Panics(t, func() { 38 | Decode("", "none") 39 | }) 40 | } 41 | 42 | func TestNegDecodeBadEncryptionMethod(t *testing.T) { 43 | assert.Panics(t, func() { 44 | Decode("cipherText", "does-not-exist") 45 | }) 46 | } 47 | 48 | func TestDecodeAsUtf16(t *testing.T) { 49 | cipherText := Encode("plainText", "none") 50 | plainText := DecodeAsUtf16(cipherText, "none") 51 | assert.Equal(t, []byte{0x70, 0x0, 0x6c, 0x0, 0x61, 0x0, 0x69, 0x0, 0x6e, 0x0, 0x54, 0x0, 0x65, 0x0, 0x78, 0x0, 0x74, 0x0}, plainText) 52 | } 53 | 54 | // TestEncryptionMethodsForUsage ensures at least "none" is an available 55 | // encryption method for usage. 56 | func TestEncryptionMethodsForUsage(t *testing.T) { 57 | s := EncryptionMethodsForUsage() 58 | assert.Contains(t, s, "none") 59 | } 60 | 61 | func TestIsValidEncryptionMethod(t *testing.T) { 62 | assert.True(t, IsValidEncryptionMethod("none")) 63 | assert.False(t, IsValidEncryptionMethod("does-not-exist")) 64 | } 65 | -------------------------------------------------------------------------------- /internal/sql/error.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sql 5 | 6 | var errorCallback func(err error) 7 | 8 | func checkErr(err error) { 9 | errorCallback(err) 10 | } 11 | -------------------------------------------------------------------------------- /internal/sql/error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sql 5 | 6 | import ( 7 | "errors" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func Test_checkErr(t *testing.T) { 13 | assert.Panics(t, func() { 14 | checkErr(errors.New("verify error handler")) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /internal/sql/factory.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sql 5 | 6 | type SqlOptions struct { 7 | UnitTesting bool 8 | } 9 | 10 | func New(options SqlOptions) Sql { 11 | if options.UnitTesting { 12 | return &mock{} 13 | } else { 14 | return &mssql{} 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /internal/sql/initialize.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sql 5 | 6 | var decryptCallback func(cipherText string, encryptionMethod string) (secret string) 7 | 8 | func Initialize( 9 | errorHandler func(err error), 10 | traceHandler func(format string, a ...any), 11 | decryptHandler func(cipherText string, encryptionMethod string) (secret string)) { 12 | errorCallback = errorHandler 13 | traceCallback = traceHandler 14 | decryptCallback = decryptHandler 15 | } 16 | -------------------------------------------------------------------------------- /internal/sql/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sql 5 | 6 | import ( 7 | . "github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig" 8 | ) 9 | 10 | type Sql interface { 11 | Connect(endpoint Endpoint, user *User, options ConnectOptions) 12 | Query(text string) 13 | ScalarString(query string) string 14 | } 15 | 16 | type ConnectOptions struct { 17 | Database string 18 | 19 | Interactive bool 20 | } 21 | -------------------------------------------------------------------------------- /internal/sql/mock.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sql 5 | 6 | import . "github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig" 7 | 8 | // Connect is a mock implementation used to speed up unit testing of other units 9 | func (m *mock) Connect( 10 | endpoint Endpoint, 11 | user *User, 12 | options ConnectOptions, 13 | ) { 14 | } 15 | 16 | // Query is a mock implementation used to speed up unit testing of other units 17 | func (m *mock) Query(text string) { 18 | } 19 | 20 | func (m *mock) ScalarString(query string) string { 21 | return "" 22 | } 23 | -------------------------------------------------------------------------------- /internal/sql/mock_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sql 5 | 6 | import "testing" 7 | import . "github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig" 8 | 9 | func TestMockConnect(t *testing.T) { 10 | mockObj := mock{} 11 | mockObj.Connect(Endpoint{}, nil, ConnectOptions{}) 12 | } 13 | 14 | func TestMockQuery(t *testing.T) { 15 | mockObj := mock{} 16 | mockObj.Query("") 17 | } 18 | -------------------------------------------------------------------------------- /internal/sql/trace.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sql 5 | 6 | var traceCallback func(format string, a ...any) 7 | 8 | func trace(format string, a ...any) { 9 | traceCallback(format, a...) 10 | } 11 | -------------------------------------------------------------------------------- /internal/sql/types.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sql 5 | 6 | import "github.com/microsoft/go-sqlcmd/pkg/sqlcmd" 7 | 8 | // mssql implements for SQL Server 9 | type mssql struct { 10 | sqlcmd *sqlcmd.Sqlcmd 11 | console sqlcmd.Console 12 | } 13 | 14 | // mock impoements for unit testing which uses a Hello World container (no 15 | // SQL) 16 | type mock struct{} 17 | -------------------------------------------------------------------------------- /internal/test/executor.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package test 5 | 6 | import ( 7 | "os" 8 | "strings" 9 | ) 10 | 11 | func IsRunningInTestExecutor() bool { 12 | if strings.HasSuffix(os.Args[0], ".test") || // are we in go test on *nix? 13 | strings.HasSuffix(os.Args[0], ".test.exe") || // are we in go test on windows? 14 | (len(os.Args) > 1 && os.Args[1] == "-test.v") { // are we in goland unittest? 15 | return true 16 | } else { 17 | return false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/test/executor_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package test 5 | 6 | import ( 7 | "github.com/stretchr/testify/assert" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func TestIsRunningInTestExecutor(t *testing.T) { 13 | // Test for running in go test on *nix 14 | os.Args[0] = "main.test" 15 | assert.True(t, IsRunningInTestExecutor(), "Failed to detect running in go test on *nix") 16 | 17 | // Test for running in go test on windows 18 | os.Args[0] = "main.test.exe" 19 | assert.True(t, IsRunningInTestExecutor(), "Failed to detect running in go test on windows") 20 | 21 | // Test for running in goland unittest 22 | os.Args = []string{"main", "-test.v"} 23 | assert.True(t, IsRunningInTestExecutor(), "Failed to detect running in goland unittest") 24 | 25 | // Test for not running in test executor 26 | os.Args = []string{"main"} 27 | assert.False(t, IsRunningInTestExecutor(), "Incorrectly detected running in test executor") 28 | 29 | // Test for invalid arguments 30 | os.Args = []string{"main", "-invalid"} 31 | assert.False(t, IsRunningInTestExecutor(), "Incorrectly detected running in test executor") 32 | } 33 | -------------------------------------------------------------------------------- /internal/tools/factory.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package tools 5 | 6 | import ( 7 | "fmt" 8 | "github.com/microsoft/go-sqlcmd/internal/tools/tool" 9 | ) 10 | 11 | var calledBefore bool 12 | 13 | func NewTool(toolName string) (tool tool.Tool) { 14 | if !calledBefore { 15 | // Init all the tools 16 | for _, t := range tools { 17 | t.Init() 18 | } 19 | calledBefore = true 20 | } 21 | 22 | // Return the asked for tool 23 | for _, t := range tools { 24 | if t.Name() == toolName { 25 | tool = t 26 | } 27 | } 28 | 29 | if tool == nil { 30 | panic(fmt.Sprintf("Tool %q is not a supported tool", toolName)) 31 | } 32 | 33 | return tool 34 | } 35 | -------------------------------------------------------------------------------- /internal/tools/factory_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package tools 5 | 6 | import ( 7 | "fmt" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | 11 | "github.com/microsoft/go-sqlcmd/internal/tools/tool" 12 | ) 13 | 14 | type TestTool struct { 15 | tool.Tool 16 | name string 17 | } 18 | 19 | func (t *TestTool) Name() string { 20 | return t.name 21 | } 22 | 23 | func (t *TestTool) Init() {} 24 | 25 | func TestNewTool(t *testing.T) { 26 | // Create some test tools 27 | tool1 := &TestTool{name: "tool1"} 28 | tool2 := &TestTool{name: "tool2"} 29 | 30 | // Set up the list of tools 31 | tools = []tool.Tool{tool1, tool2} 32 | 33 | // Test that we get back the right tool 34 | result := NewTool("tool1") 35 | assert.Equal(t, result, tool1, "Expected tool1 but got %v", result) 36 | 37 | // Test that we get an error for an unsupported tool 38 | expectedErr := fmt.Sprintf("Tool %q is not a supported tool", "unsupported") 39 | defer func() { 40 | if r := recover(); r == nil { 41 | t.Errorf("Expected panic but didn't get one") 42 | } else if r != expectedErr { 43 | t.Errorf("Expected panic message %q but got %q", expectedErr, r) 44 | } 45 | }() 46 | 47 | _ = NewTool("unsupported") 48 | } 49 | -------------------------------------------------------------------------------- /internal/tools/tool/ads.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package tool 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/io/file" 8 | "github.com/microsoft/go-sqlcmd/internal/test" 9 | ) 10 | 11 | type AzureDataStudio struct { 12 | tool 13 | } 14 | 15 | func (t *AzureDataStudio) Init() { 16 | t.tool.SetToolDescription(Description{ 17 | Name: "ads", 18 | Purpose: "Azure Data Studio is a database tool for data professionals who use on-premises and cloud data platforms.", 19 | InstallText: t.installText()}) 20 | 21 | for _, location := range t.searchLocations() { 22 | if file.Exists(location) { 23 | t.tool.SetExePathAndName(location) 24 | break 25 | } 26 | } 27 | } 28 | 29 | func (t *AzureDataStudio) Run(args []string) (int, error) { 30 | if !test.IsRunningInTestExecutor() { 31 | return t.tool.Run(args) 32 | } else { 33 | return 0, nil 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /internal/tools/tool/ads_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package tool 5 | 6 | import ( 7 | "os" 8 | "path/filepath" 9 | ) 10 | 11 | func (t *AzureDataStudio) searchLocations() []string { 12 | userProfile := os.Getenv("HOME") 13 | 14 | return []string{ 15 | filepath.Join("/", "Applications", "Azure Data Studio - Insiders.app"), 16 | filepath.Join(userProfile, "Downloads", "Azure Data Studio - Insiders.app"), 17 | filepath.Join("/", "Applications", "Azure Data Studio.app"), 18 | filepath.Join(userProfile, "Downloads", "Azure Data Studio.app"), 19 | } 20 | } 21 | 22 | func (t *AzureDataStudio) installText() string { 23 | return `Download the latest .zip from: 24 | 25 | https://go.microsoft.com/fwlink/?linkid=2151311 26 | 27 | More information can be found here: 28 | 29 | https://docs.microsoft.com/sql/azure-data-studio/download-azure-data-studio?#get-azure-data-studio-for-macos` 30 | } 31 | -------------------------------------------------------------------------------- /internal/tools/tool/ads_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package tool 5 | 6 | func (t *AzureDataStudio) searchLocations() []string { 7 | panic("Not yet implemented") 8 | } 9 | 10 | func (t *AzureDataStudio) installText() string { 11 | return `Follow the instructions here: 12 | 13 | https://docs.microsoft.com/sql/azure-data-studio/download-azure-data-studio?#get-azure-data-studio-for-linux` 14 | } 15 | -------------------------------------------------------------------------------- /internal/tools/tool/ads_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package tool 5 | 6 | import ( 7 | "github.com/stretchr/testify/assert" 8 | "path/filepath" 9 | "runtime" 10 | "testing" 11 | ) 12 | 13 | func TestAzureDataStudio_Init(t *testing.T) { 14 | if runtime.GOOS == "linux" { 15 | t.Skip("Not yet implemented on Linux") 16 | } 17 | t.Parallel() 18 | 19 | t.Run("found", func(t *testing.T) { 20 | t.Parallel() 21 | 22 | ads := &AzureDataStudio{} 23 | ads.Init() 24 | 25 | filepath.Base(ads.exeName) 26 | }) 27 | 28 | } 29 | 30 | func TestAzureDataStudio_Run(t *testing.T) { 31 | if runtime.GOOS == "linux" { 32 | t.Skip("Not yet implemented on Linux") 33 | } 34 | t.Parallel() 35 | 36 | ads := &AzureDataStudio{} 37 | ads.Init() 38 | ads.IsInstalled() 39 | _, _ = ads.Run(nil) 40 | } 41 | 42 | func TestAzureDataStudio_searchLocations(t *testing.T) { 43 | if runtime.GOOS == "linux" { 44 | t.Skip("Not yet implemented on Linux") 45 | } 46 | t.Parallel() 47 | 48 | got := (&AzureDataStudio{}).searchLocations() 49 | 50 | assert.GreaterOrEqual(t, len(got), 1, "expecting 1 or search locations for Azure Data Studio on Windows, got %d", len(got)) 51 | } 52 | 53 | func TestAzureDataStudio_installText(t *testing.T) { 54 | if runtime.GOOS == "linux" { 55 | t.Skip("Not yet implemented on Linux") 56 | } 57 | 58 | t.Parallel() 59 | 60 | got := (&AzureDataStudio{}).installText() 61 | 62 | assert.GreaterOrEqual(t, len(got), 1, "no install text provided") 63 | } 64 | -------------------------------------------------------------------------------- /internal/tools/tool/ads_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package tool 5 | 6 | import ( 7 | "os" 8 | "path/filepath" 9 | ) 10 | 11 | // Search in this order 12 | // 13 | // User Insiders Install 14 | // System Insiders Install 15 | // User non-Insiders install 16 | // System non-Insiders install 17 | func (t *AzureDataStudio) searchLocations() []string { 18 | userProfile := os.Getenv("USERPROFILE") 19 | programFiles := os.Getenv("ProgramFiles") 20 | 21 | return []string{ 22 | filepath.Join(userProfile, "AppData\\Local\\Programs\\Azure Data Studio - Insiders\\azuredatastudio-insiders.exe"), 23 | filepath.Join(programFiles, "Azure Data Studio - Insiders\\azuredatastudio-insiders.exe"), 24 | filepath.Join(userProfile, "AppData\\Local\\Programs\\Azure Data Studio\\azuredatastudio.exe"), 25 | filepath.Join(programFiles, "Azure Data Studio\\azuredatastudio.exe"), 26 | } 27 | } 28 | 29 | func (t *AzureDataStudio) installText() string { 30 | return `Download the latest 'User Installer' .msi from: 31 | 32 | https://go.microsoft.com/fwlink/?linkid=2150927 33 | 34 | More information can be found here: 35 | 36 | https://docs.microsoft.com/sql/azure-data-studio/download-azure-data-studio#get-azure-data-studio-for-windows` 37 | } 38 | -------------------------------------------------------------------------------- /internal/tools/tool/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package tool 5 | 6 | type Tool interface { 7 | Init() 8 | Name() (name string) 9 | Run(args []string) (exitCode int, err error) 10 | IsInstalled() bool 11 | HowToInstall() string 12 | } 13 | -------------------------------------------------------------------------------- /internal/tools/tool/tool.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package tool 5 | 6 | import ( 7 | "fmt" 8 | "strings" 9 | 10 | "github.com/microsoft/go-sqlcmd/internal/io/file" 11 | ) 12 | 13 | func (t *tool) Init() { 14 | panic("Do not call directly") 15 | } 16 | 17 | func (t *tool) Name() string { 18 | return t.description.Name 19 | } 20 | 21 | func (t *tool) SetExePathAndName(exeName string) { 22 | t.exeName = exeName 23 | } 24 | 25 | func (t *tool) SetToolDescription(description Description) { 26 | t.description = description 27 | } 28 | 29 | func (t *tool) IsInstalled() bool { 30 | if t.installed != nil { 31 | return *t.installed 32 | } 33 | 34 | t.installed = new(bool) 35 | if file.Exists(t.exeName) { 36 | *t.installed = true 37 | } else { 38 | *t.installed = false 39 | } 40 | 41 | return *t.installed 42 | } 43 | 44 | func (t *tool) HowToInstall() string { 45 | var sb strings.Builder 46 | 47 | sb.WriteString("\n\n") 48 | sb.WriteString(fmt.Sprintf("%q is not installed on this machine.\n\n", t.description.Name)) 49 | sb.WriteString(fmt.Sprintf("%v\n\n", t.description.Purpose)) 50 | sb.WriteString(fmt.Sprintf("To install %q...\n\n%v\n", t.description.Name, t.description.InstallText)) 51 | 52 | return sb.String() 53 | } 54 | 55 | func (t *tool) Run(args []string) (int, error) { 56 | if t.installed == nil { 57 | panic("Call IsInstalled before Run") 58 | } 59 | 60 | cmd := t.generateCommandLine(args) 61 | err := cmd.Run() 62 | 63 | return cmd.ProcessState.ExitCode(), err 64 | } 65 | -------------------------------------------------------------------------------- /internal/tools/tool/tool_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package tool 5 | 6 | import ( 7 | "bytes" 8 | "os/exec" 9 | ) 10 | 11 | func (t *tool) generateCommandLine(args []string) *exec.Cmd { 12 | path, _ := exec.LookPath("open") 13 | 14 | args = append([]string{"--args"}, args...) 15 | args = append([]string{t.exeName}, args...) 16 | args = append([]string{"-a"}, args...) 17 | 18 | var stdout, stderr bytes.Buffer 19 | cmd := &exec.Cmd{ 20 | Path: path, 21 | Args: args, 22 | Stdout: &stdout, 23 | Stderr: &stderr, 24 | } 25 | return cmd 26 | } 27 | -------------------------------------------------------------------------------- /internal/tools/tool/tool_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package tool 5 | 6 | import ( 7 | "os/exec" 8 | ) 9 | 10 | func (t *tool) generateCommandLine(args []string) *exec.Cmd { 11 | panic("Not yet implemented") 12 | } 13 | -------------------------------------------------------------------------------- /internal/tools/tool/tool_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package tool 5 | 6 | import ( 7 | "bytes" 8 | "os/exec" 9 | ) 10 | 11 | func (t *tool) generateCommandLine(args []string) *exec.Cmd { 12 | var stdout, stderr bytes.Buffer 13 | cmd := &exec.Cmd{ 14 | Path: t.exeName, 15 | Args: args, 16 | Stdout: &stdout, 17 | Stderr: &stderr, 18 | } 19 | return cmd 20 | } 21 | -------------------------------------------------------------------------------- /internal/tools/tool/types.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package tool 5 | 6 | type tool struct { 7 | installed *bool 8 | lookPathError error 9 | exeName string 10 | description Description 11 | } 12 | 13 | type Description struct { 14 | // Name of the tool, e.g. "Azure Data Studio" 15 | Name string 16 | 17 | // Purpose describes what this tool does 18 | Purpose string 19 | 20 | // How to install the tool, e.g. what URL to go to, to download it 21 | InstallText string 22 | } 23 | -------------------------------------------------------------------------------- /internal/tools/tools.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package tools 5 | 6 | import ( 7 | "github.com/microsoft/go-sqlcmd/internal/tools/tool" 8 | ) 9 | 10 | var tools = []tool.Tool{ 11 | &tool.AzureDataStudio{}, 12 | } 13 | -------------------------------------------------------------------------------- /internal/translations/LocProject.json: -------------------------------------------------------------------------------- 1 | { 2 | "Projects": [ 3 | { 4 | "LanguageSet": "AzureKatal_Languages", 5 | "LocItems": [ 6 | { 7 | "SourceFile": "internal\\translations\\locales\\en-US\\out.gotext.json", 8 | "CopyOption": "LangIDOnPath", 9 | "Languages": "de-DE;fr-FR;it-IT;es-ES;ja-JP;ko-KR,zh-CN,zh-TW,pt-BR,ru-RU", 10 | "LssFiles": ["internal\\translations\\P306PairNamesToProcess.lss"], 11 | "LclFile": "internal\\translations\\LCL\\{Lang}\\out.gotext.json.lcl", 12 | "OutputPath": "internal\\translations\\localized" 13 | } 14 | ] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /internal/translations/P306PairNamesToProcess.lss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /internal/translations/catalog_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package translations 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | // TestLookup tests the Lookup method of the dictionary type. 13 | func TestLookup(t *testing.T) { 14 | d := &dictionary{ 15 | index: []uint32{0, 10, 11, 12, 13, 14, 15}, 16 | data: "abcdefghijklmnopqrstuvwxyz", 17 | } 18 | 19 | testCases := []struct { 20 | name string 21 | key string 22 | expected string 23 | ok bool 24 | }{ 25 | { 26 | name: "non-existing key", 27 | key: "non-existing key", 28 | expected: "", 29 | ok: false, 30 | }, 31 | { 32 | name: "existing key", 33 | key: "View configuration information and connection strings", 34 | expected: "k", 35 | ok: true, 36 | }, 37 | } 38 | 39 | for _, tc := range testCases { 40 | t.Run(tc.name, func(t *testing.T) { 41 | actual, ok := d.Lookup(tc.key) 42 | assert.Equal(t, tc.expected, actual) 43 | assert.Equal(t, tc.ok, ok) 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /internal/translations/translations.go: -------------------------------------------------------------------------------- 1 | package translations 2 | 3 | //go:generate gotext -srclang=en-US update -out=catalog.go -lang=en-US,de-DE,fr-FR,zh-CN,zh-TW,it-IT,ja-JP,ko-KR,pt-BR,ru-RU,es-ES github.com/microsoft/go-sqlcmd/cmd/modern github.com/microsoft/go-sqlcmd/cmd/sqlcmd github.com/microsoft/go-sqlcmd/pkg/sqlcmd 4 | -------------------------------------------------------------------------------- /pkg/console/console_redirect.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package console 5 | 6 | import ( 7 | "os" 8 | "golang.org/x/term" 9 | ) 10 | 11 | // isStdinRedirected checks if stdin is coming from a pipe or redirection 12 | func isStdinRedirected() bool { 13 | stat, err := os.Stdin.Stat() 14 | if err != nil { 15 | // If we can't determine, assume it's not redirected 16 | return false 17 | } 18 | 19 | // If it's not a character device, it's coming from a pipe or redirection 20 | if (stat.Mode() & os.ModeCharDevice) == 0 { 21 | return true 22 | } 23 | 24 | // Double-check using term.IsTerminal 25 | fd := int(os.Stdin.Fd()) 26 | return !term.IsTerminal(fd) 27 | } -------------------------------------------------------------------------------- /pkg/console/console_redirect_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package console 5 | 6 | import ( 7 | "io" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func TestStdinRedirectionDetection(t *testing.T) { 13 | // Save original stdin 14 | origStdin := os.Stdin 15 | defer func() { os.Stdin = origStdin }() 16 | 17 | // Create a pipe 18 | r, w, err := os.Pipe() 19 | if err != nil { 20 | t.Fatalf("Couldn't create pipe: %v", err) 21 | } 22 | defer r.Close() 23 | defer w.Close() 24 | 25 | // Replace stdin with our pipe 26 | os.Stdin = r 27 | 28 | // Test if stdin is properly detected as redirected 29 | if !isStdinRedirected() { 30 | t.Errorf("Pipe input should be detected as redirected") 31 | } 32 | 33 | // Write some test input 34 | go func() { 35 | _, _ = io.WriteString(w, "test input\n") 36 | w.Close() 37 | }() 38 | 39 | // Create console with redirected stdin 40 | console := NewConsole("") 41 | 42 | // Test readline 43 | line, err := console.Readline() 44 | if err != nil { 45 | t.Fatalf("Failed to read from redirected stdin: %v", err) 46 | } 47 | 48 | if line != "test input" { 49 | t.Errorf("Expected 'test input', got '%s'", line) 50 | } 51 | 52 | // Clean up 53 | console.Close() 54 | } -------------------------------------------------------------------------------- /pkg/sqlcmd-linter/imports_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sqlcmdlinter 5 | 6 | import ( 7 | "os" 8 | "path/filepath" 9 | "testing" 10 | 11 | "golang.org/x/tools/go/analysis/analysistest" 12 | ) 13 | 14 | func TestImports(t *testing.T) { 15 | wd, err := os.Getwd() 16 | if err != nil { 17 | t.Errorf("Failed to get wd: %s", err) 18 | } 19 | analysistest.Run(t, filepath.Join(wd, `testdata`), ImportsAnalyzer, "imports_linter_tests/...") 20 | } 21 | -------------------------------------------------------------------------------- /pkg/sqlcmd-linter/testdata/src/github.com/README.md: -------------------------------------------------------------------------------- 1 | This directory structure exists to provide stub package implementations for the linter tests. The `analysistest` package replaces `$GOPATH` with the local file system path. We create these stubs so the linter test files can closely mimic our production code. -------------------------------------------------------------------------------- /pkg/sqlcmd-linter/testdata/src/github.com/alecthomas/chroma/chroma.go: -------------------------------------------------------------------------------- 1 | package chroma 2 | 3 | var C = 1 4 | -------------------------------------------------------------------------------- /pkg/sqlcmd-linter/testdata/src/github.com/microsoft/go-sqlcmd/pkg/sqlcmd/sqlcmd.go: -------------------------------------------------------------------------------- 1 | package sqlcmd 2 | 3 | var S = 1 4 | -------------------------------------------------------------------------------- /pkg/sqlcmd-linter/testdata/src/github.com/stretchr/testify/assert/main.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import "testing" 4 | 5 | func NotNil(t *testing.T, object interface{}, msgAndArgs ...interface{}) bool { 6 | return false 7 | } 8 | 9 | func NoError(t *testing.T, err error, msgAndArgs ...interface{}) bool { 10 | return false 11 | } 12 | -------------------------------------------------------------------------------- /pkg/sqlcmd-linter/testdata/src/imports_linter_tests/cmd/main/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package main 5 | 6 | import ( 7 | _ "github.com/alecthomas/chroma" // want "Non-internal packages should not import \"github.com/alecthomas/chroma\"" 8 | _ "github.com/microsoft/go-sqlcmd/pkg/sqlcmd" 9 | _ "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | var X = 1 13 | -------------------------------------------------------------------------------- /pkg/sqlcmd-linter/testdata/src/imports_linter_tests/internal/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package main 5 | 6 | import ( 7 | _ "fmt" 8 | 9 | _ "github.com/microsoft/go-sqlcmd/pkg/sqlcmd" // want "Internal packages should not import \"github.com/microsoft/go-sqlcmd/pkg/sqlcmd\"" 10 | ) 11 | 12 | var X = 1 13 | -------------------------------------------------------------------------------- /pkg/sqlcmd-linter/testdata/src/useassert_linter_tests/assert.go: -------------------------------------------------------------------------------- 1 | package sqlcmd 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestDontUseFatal(t *testing.T) { 11 | t.Fatal("this should fail") // want "Use assert package methods instead of Fatal" 12 | t.Fatalf("this should %s", "fail") // want "Use assert package methods instead of Fatalf" 13 | t.Fail() // want "Use assert package methods instead of Fail" 14 | assert.NoError(t, fmt.Errorf("what")) 15 | t.FailNow() // want "Use assert package methods instead of FailNow" 16 | 17 | } 18 | 19 | func TestDontUseRecover(t *testing.T) { 20 | defer func() { assert.NotNil(t, recover(), "The code did not panic as expected") }() // want "Use assert.Panics instead of recover()" 21 | } 22 | -------------------------------------------------------------------------------- /pkg/sqlcmd-linter/useasserts.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sqlcmdlinter 5 | 6 | import ( 7 | "go/ast" 8 | 9 | "golang.org/x/tools/go/analysis" 10 | "golang.org/x/tools/go/analysis/passes/inspect" 11 | "golang.org/x/tools/go/ast/inspector" 12 | ) 13 | 14 | var AssertAnalyzer = &analysis.Analyzer{ 15 | Name: "assertlint", 16 | Doc: "Require use of asserts instead of fmt.Error functions in tests", 17 | Requires: []*analysis.Analyzer{inspect.Analyzer}, 18 | Run: runAsserts, 19 | } 20 | 21 | var blockedTestingMethods = []string{"Error", "ErrorF", "Fail", "FailNow", "Fatal", "Fatalf"} 22 | 23 | func runAsserts(pass *analysis.Pass) (interface{}, error) { 24 | // pass.ResultOf[inspect.Analyzer] will be set if we've added inspect.Analyzer to Requires. 25 | // Analyze code and make an AST from the file: 26 | inspectorInstance := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 27 | 28 | nodeFilter := []ast.Node{(*ast.CallExpr)(nil)} 29 | 30 | inspectorInstance.Preorder(nodeFilter, func(n ast.Node) { 31 | node := n.(*ast.CallExpr) 32 | switch fun := node.Fun.(type) { 33 | case (*ast.SelectorExpr): 34 | switch funX := fun.X.(type) { 35 | case (*ast.Ident): 36 | if funX.Name == "t" && contains(blockedTestingMethods, fun.Sel.Name) { 37 | pass.Reportf(node.Pos(), "Use assert package methods instead of %s", fun.Sel.Name) 38 | } 39 | default: 40 | return 41 | } 42 | case (*ast.Ident): 43 | if fun.Name == "recover" { 44 | pass.Reportf(node.Pos(), "Use assert.Panics instead of recover()") 45 | } 46 | } 47 | }) 48 | return nil, nil 49 | } 50 | 51 | func contains(a []string, v string) bool { 52 | for _, val := range a { 53 | if val == v { 54 | return true 55 | } 56 | } 57 | return false 58 | } 59 | -------------------------------------------------------------------------------- /pkg/sqlcmd-linter/useasserts_test.go: -------------------------------------------------------------------------------- 1 | package sqlcmdlinter 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "golang.org/x/tools/go/analysis/analysistest" 9 | ) 10 | 11 | func TestUseAsserts(t *testing.T) { 12 | wd, err := os.Getwd() 13 | if err != nil { 14 | t.Errorf("Failed to get wd: %s", err) 15 | } 16 | analysistest.Run(t, filepath.Join(wd, `testdata`), AssertAnalyzer, "useassert_linter_tests/...") 17 | } 18 | -------------------------------------------------------------------------------- /pkg/sqlcmd/azure_auth.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sqlcmd 5 | 6 | import ( 7 | "database/sql/driver" 8 | "fmt" 9 | "net/url" 10 | "os" 11 | 12 | "github.com/microsoft/go-mssqldb/azuread" 13 | ) 14 | 15 | const ( 16 | NotSpecified = "NotSpecified" 17 | SqlPassword = "SqlPassword" 18 | sqlClientId = "a94f9c62-97fe-4d19-b06d-472bed8d2bcf" 19 | ) 20 | 21 | func getSqlClientId() string { 22 | if clientId := os.Getenv("SQLCMDCLIENTID"); clientId != "" { 23 | return clientId 24 | } 25 | return sqlClientId 26 | } 27 | 28 | func GetTokenBasedConnection(connstr string, authenticationMethod string) (driver.Connector, error) { 29 | 30 | connectionUrl, err := url.Parse(connstr) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | query := connectionUrl.Query() 36 | query.Set("fedauth", authenticationMethod) 37 | query.Set("applicationclientid", getSqlClientId()) 38 | switch authenticationMethod { 39 | case azuread.ActiveDirectoryServicePrincipal, azuread.ActiveDirectoryApplication: 40 | query.Set("clientcertpath", os.Getenv("AZURE_CLIENT_CERTIFICATE_PATH")) 41 | case azuread.ActiveDirectoryInteractive: 42 | case azuread.ActiveDirectoryDeviceCode: 43 | loginTimeout := query.Get("connection timeout") 44 | loginTimeoutSeconds := 0 45 | if loginTimeout != "" { 46 | _, _ = fmt.Sscanf(loginTimeout, "%d", &loginTimeoutSeconds) 47 | } 48 | // AAD interactive needs minutes at minimum 49 | if loginTimeoutSeconds > 0 && loginTimeoutSeconds < 120 { 50 | query.Set("connection timeout", "120") 51 | } 52 | } 53 | 54 | connectionUrl.RawQuery = query.Encode() 55 | return azuread.NewConnector(connectionUrl.String()) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/sqlcmd/exec_darwin.go: -------------------------------------------------------------------------------- 1 | package sqlcmd 2 | 3 | import ( 4 | "os/exec" 5 | ) 6 | 7 | func sysCommand(arg string) *exec.Cmd { 8 | cmd := exec.Command(comSpec(), "-c", arg) 9 | return cmd 10 | } 11 | 12 | // comSpec returns the path of the command shell executable 13 | func comSpec() string { 14 | // /bin/sh will be a link to the shell 15 | return `/bin/sh` 16 | } 17 | 18 | const defaultEditor = "vi" 19 | -------------------------------------------------------------------------------- /pkg/sqlcmd/exec_linux.go: -------------------------------------------------------------------------------- 1 | package sqlcmd 2 | 3 | import ( 4 | "os/exec" 5 | ) 6 | 7 | func sysCommand(arg string) *exec.Cmd { 8 | cmd := exec.Command(comSpec(), "-c", arg) 9 | return cmd 10 | } 11 | 12 | // comSpec returns the path of the command shell executable 13 | func comSpec() string { 14 | // /bin/sh will be a link to the shell 15 | return `/bin/sh` 16 | } 17 | 18 | const defaultEditor = "vi" 19 | -------------------------------------------------------------------------------- /pkg/sqlcmd/exec_windows.go: -------------------------------------------------------------------------------- 1 | package sqlcmd 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | "syscall" 7 | ) 8 | 9 | func sysCommand(arg string) *exec.Cmd { 10 | cmd := exec.Command(comSpec()) 11 | cmd.SysProcAttr = &syscall.SysProcAttr{CmdLine: cmd.Path + " " + comArgs(arg)} 12 | return cmd 13 | } 14 | 15 | // comSpec returns the path of the command shell executable 16 | func comSpec() string { 17 | if cmd, ok := os.LookupEnv("COMSPEC"); ok { 18 | return cmd 19 | } 20 | return `C:\Windows\System32\cmd.exe` 21 | } 22 | 23 | func comArgs(args string) string { 24 | return `/c ` + args 25 | } 26 | 27 | const defaultEditor = "notepad.exe" 28 | -------------------------------------------------------------------------------- /pkg/sqlcmd/format_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sqlcmd 5 | 6 | // SqlcmdEol is the end-of-line marker for sqlcmd output 7 | const SqlcmdEol = "\n" 8 | -------------------------------------------------------------------------------- /pkg/sqlcmd/format_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sqlcmd 5 | 6 | // SqlcmdEol is the end-of-line marker for sqlcmd output 7 | const SqlcmdEol = "\n" 8 | -------------------------------------------------------------------------------- /pkg/sqlcmd/format_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sqlcmd 5 | 6 | // SqlcmdEol is the end-of-line marker for sqlcmd output 7 | const SqlcmdEol = "\r\n" 8 | -------------------------------------------------------------------------------- /pkg/sqlcmd/parse.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sqlcmd 5 | 6 | import ( 7 | "strings" 8 | "unicode" 9 | ) 10 | 11 | // grab grabs i from r, or returns 0 if i >= end. 12 | func grab(r []rune, i, end int) rune { 13 | if i < end { 14 | return r[i] 15 | } 16 | return 0 17 | } 18 | 19 | // readMultilineComment finds the end of a multiline comment (ie, '*/'). 20 | func readMultilineComment(r []rune, i, end int) (int, bool) { 21 | i++ 22 | for ; i < end; i++ { 23 | if r[i-1] == '*' && r[i] == '/' { 24 | return i, true 25 | } 26 | } 27 | return end, false 28 | } 29 | 30 | // readCommand reads to the next control character to find 31 | // a command in the string. Command regexes constrain matches 32 | // to the beginning of the string, and all commands consume 33 | // an entire line. 34 | func readCommand(c Commands, r []rune, i, end int) (*Command, []string, int) { 35 | for ; i < end; i++ { 36 | next := grab(r, i, end) 37 | if next == 0 || (unicode.IsControl(next) && next != '\t') { 38 | break 39 | } 40 | } 41 | cmd, args := c.matchCommand(string(r[:i])) 42 | return cmd, args, i 43 | } 44 | 45 | // readVariableReference returns the index of the end of the variable reference or false if it's not a valid identifier 46 | func readVariableReference(r []rune, i int, end int) (int, bool) { 47 | for ; i < end; i++ { 48 | if r[i] == ')' { 49 | return i, true 50 | } 51 | if (r[i] >= 'a' && r[i] <= 'z') || (r[i] >= 'A' && r[i] <= 'Z') || (r[i] >= '0' && r[i] <= '9') || strings.ContainsRune(validVariableRunes, r[i]) { 52 | continue 53 | } 54 | break 55 | } 56 | return 0, false 57 | } 58 | 59 | func max64(a, b int64) int64 { 60 | if a > b { 61 | return a 62 | } 63 | return b 64 | } 65 | 66 | // min returns the minimum of a, b. 67 | func min(a, b int) int { 68 | if a < b { 69 | return a 70 | } 71 | return b 72 | } 73 | 74 | func min64(a, b int64) int64 { 75 | if a < b { 76 | return a 77 | } 78 | return b 79 | } 80 | -------------------------------------------------------------------------------- /pkg/sqlcmd/parse_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package sqlcmd 5 | -------------------------------------------------------------------------------- /pkg/sqlcmd/sqlcmd_windows_amd64.go: -------------------------------------------------------------------------------- 1 | package sqlcmd 2 | 3 | import ( 4 | "github.com/microsoft/go-mssqldb/msdsn" 5 | _ "github.com/microsoft/go-mssqldb/namedpipe" 6 | _ "github.com/microsoft/go-mssqldb/sharedmemory" 7 | ) 8 | 9 | // Note: The order of includes above matters for namedpipe and sharedmemory. 10 | // Go tools always sort by name. 11 | // init() swaps shared memory protocol with np so it gets priority when dialing. 12 | 13 | func init() { 14 | if len(msdsn.ProtocolParsers) == 4 { 15 | // reorder the protocol parsers to tcp->admin->lpc->np 16 | // Named pipes/shared memory package doesn't support ARM 17 | // Once there's a fix for https://github.com/natefinch/npipe/issues/34 incorporated into go-mssqldb, 18 | // reorder to lpc->admin->np->tcp 19 | var lpc = msdsn.ProtocolParsers[3] 20 | msdsn.ProtocolParsers[3] = msdsn.ProtocolParsers[2] 21 | msdsn.ProtocolParsers[2] = lpc 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pkg/sqlcmd/testdata/blanks.sql: -------------------------------------------------------------------------------- 1 | set nocount on 2 | :setvar l line2 3 | select 'line 1 4 | 5 | 6 | 7 | $(l)' -------------------------------------------------------------------------------- /pkg/sqlcmd/testdata/quotedidentifiers.sql: -------------------------------------------------------------------------------- 1 | set nocount on 2 | set quoted_identifier on 3 | select [a]]b] = 'ab', "a'b" = 1, [a"b] = 'a"b' 4 | -------------------------------------------------------------------------------- /pkg/sqlcmd/testdata/selectdates.sql: -------------------------------------------------------------------------------- 1 | set nocount on 2 | declare @d1 datetime = '2022-03-05 14:01:02' 3 | declare @d2 datetime2(4) = '2021-1-2 11:06:02.2' 4 | declare @d3 datetimeoffset(6) = '2021-5-5' 5 | declare @d4 smalldatetime = '2019-01-11 13:00:00' 6 | declare @d5 time = '14:01:02' 7 | declare @d6 date = '2011-02-03' 8 | 9 | select @d1, @d2, @d3, @d4, @d5, @d6 10 | -------------------------------------------------------------------------------- /pkg/sqlcmd/testdata/singlebatchnogo.sql: -------------------------------------------------------------------------------- 1 | select 100 as num 2 | select 'string' as title 3 | -------------------------------------------------------------------------------- /pkg/sqlcmd/testdata/testerrorredirection.sql: -------------------------------------------------------------------------------- 1 | select '1' 2 | go 3 | select $(var 4 | go -------------------------------------------------------------------------------- /pkg/sqlcmd/testdata/twobatchnoendinggo.sql: -------------------------------------------------------------------------------- 1 | select 100 as num 2 | go 3 | select 'string' as title 4 | -------------------------------------------------------------------------------- /pkg/sqlcmd/testdata/twobatchwithgo.sql: -------------------------------------------------------------------------------- 1 | select 100 as num 2 | GO 3 | select 'string' as title 4 | GO 5 | -------------------------------------------------------------------------------- /pkg/sqlcmd/testdata/variablesnogo.sql: -------------------------------------------------------------------------------- 1 | set nocount on 2 | :setvar hundred 100 3 | 4 | -- verify fix for https://github.com/microsoft/go-sqlcmd/issues/197 5 | 6 | -- Correctly handle the first line of a batch having a variable after an empty line 7 | 8 | GO 9 | 10 | select $(hundred) 11 | 12 | GO -------------------------------------------------------------------------------- /release/linux/deb/README.md: -------------------------------------------------------------------------------- 1 | # Debian Packaging Release 2 | 3 | ## Building the Debian package 4 | 5 | Execute the following command from the root directory of this repository: 6 | 7 | ``` bash 8 | ./release/linux/debian/pipeline.sh 9 | ``` 10 | 11 | Output will be sent to `./output/debian` 12 | 13 | ## Dev Installation and Verification 14 | 15 | ``` bash 16 | ./release/linux/debian/pipeline-test.sh 17 | ``` 18 | 19 | ## Release Install/Update/Uninstall Steps 20 | 21 | > **Note:** Replace `{{HOST}}` and `{{CLI_VERSION}}` with the appropriate values. 22 | 23 | ### Install sqlcmd with apt (Ubuntu or Debian) 24 | 25 | 1. Download and install the signing key: 26 | 27 | ```bash 28 | sudo curl -sL http://{{HOST}}/browse/repo/ubuntu/dpgswdist.v1.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/dpgswdist.v1.asc.gpg > /dev/null 29 | ``` 30 | 31 | 2. Add the sqlcmd repository information: 32 | 33 | ```bash 34 | sudo echo "deb [trusted=yes arch=amd64] http://{{HOST}}/browse/repo/ubuntu/sqlcmd mssql main" | tee /etc/apt/sources.list.d/sqlcmd.list 35 | ``` 36 | 37 | 3. Update repository information and install sqlcmd: 38 | 39 | ```bash 40 | sudo apt-get update 41 | sudo apt-get install sqlcmd 42 | ``` 43 | 44 | 5. Verify installation success: 45 | 46 | ```bash 47 | sqlcmd --help 48 | ``` 49 | 50 | ### Update 51 | 52 | 1. Upgrade sqlcmd only: 53 | 54 | ```bash 55 | sudo apt-get update && sudo apt-get install --only-upgrade -y sqlcmd 56 | ``` 57 | 58 | ### Uninstall 59 | 60 | 1. Uninstall with apt-get remove: 61 | 62 | ```bash 63 | sudo apt-get remove -y sqlcmd 64 | ``` 65 | 66 | 2. Remove the sqlcmd repository information: 67 | 68 | > Note: This step is not needed if you plan on installing sqlcmd in the future 69 | 70 | ```bash 71 | sudo rm /etc/apt/sources.list.d/sqlcmd.list 72 | ``` 73 | 74 | 3. Remove the signing key: 75 | 76 | ```bash 77 | sudo rm /etc/apt/trusted.gpg.d/dpgswdist.v1.asc.gpg 78 | ``` 79 | 80 | 4. Remove any unneeded dependencies that were installed with sqlcmd: 81 | 82 | ```bash 83 | sudo apt autoremove 84 | ``` 85 | -------------------------------------------------------------------------------- /release/linux/deb/build-pkg.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #------------------------------------------------------------------------------ 4 | # Copyright (c) Microsoft Corporation. 5 | # Licensed under the MIT license. 6 | #------------------------------------------------------------------------------ 7 | 8 | # Description: 9 | # 10 | # Build a debian/ubuntu `sqlcmd` package. This script is intended to be ran in a 11 | # container with the respective disto/image laid down. 12 | # 13 | # Usage: 14 | # $ build-pkg.sh 15 | 16 | set -exv 17 | 18 | : "${CLI_VERSION:?CLI_VERSION environment variable not set.}" 19 | : "${CLI_VERSION_REVISION:?CLI_VERSION_REVISION environment variable not set.}" 20 | 21 | WORKDIR=`cd $(dirname $0); cd ../../../; pwd` 22 | 23 | ls -la ${WORKDIR} 24 | 25 | apt-get -y update || exit 1 26 | export DEBIAN_FRONTEND=noninteractive 27 | apt-get install -y \ 28 | debhelper \ 29 | dpkg-dev \ 30 | locales || exit 1 31 | 32 | # Locale 33 | sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \ 34 | dpkg-reconfigure --frontend=noninteractive locales && \ 35 | update-locale LANG=en_US.UTF-8 36 | 37 | export LANG=en_US.UTF-8 38 | export PATH=$PATH 39 | 40 | # Verify 41 | chmod u+x /mnt/workspace/sqlcmd 42 | /mnt/workspace/sqlcmd --help 43 | 44 | mkdir /opt/stage 45 | cp /mnt/workspace/sqlcmd /opt/stage/sqlcmd 46 | 47 | # Create create directory for debian build 48 | mkdir -p ${WORKDIR}/debian 49 | ${WORKDIR}/release/linux/deb/prepare-rules.sh ${WORKDIR}/debian ${WORKDIR} 50 | 51 | cd ${WORKDIR} 52 | dpkg-buildpackage -us -uc 53 | 54 | ls ${WORKDIR} -R 55 | 56 | debPkg=${WORKDIR}/../sqlcmd_${CLI_VERSION}-${CLI_VERSION_REVISION:=1}_all.deb 57 | cp ${debPkg} /mnt/output/ 58 | -------------------------------------------------------------------------------- /release/linux/docker/README.md: -------------------------------------------------------------------------------- 1 | # Docker Release 2 | 3 | ## Building Docker in CI/CD pipeline 4 | 5 | Execute the following command from the root directory of this repository: 6 | 7 | ```bash 8 | ./release/linux/docker/pipeline.sh 9 | ``` 10 | 11 | Output will be sent to `./output/docker` 12 | 13 | ## Verify 14 | 15 | ```bash 16 | ./release/linux/docker/pipeline-test.sh 17 | ``` 18 | -------------------------------------------------------------------------------- /release/linux/docker/pipeline-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #------------------------------------------------------------------------------ 4 | # Copyright (c) Microsoft Corporation. 5 | # Licensed under the MIT license. 6 | #------------------------------------------------------------------------------ 7 | 8 | # Description: 9 | # 10 | # Instructions to be invoked under the build CI pipeline in AzureDevOps. 11 | # 12 | # Kickoff docker image test: 13 | # 14 | # Usage: 15 | # 16 | # $ pipeline-test.sh 17 | 18 | set -exv 19 | 20 | : "${REPO_ROOT_DIR:=`cd $(dirname $0); cd ../../../; pwd`}" 21 | 22 | PACKAGE_VERSION=${CLI_VERSION:=0.0.1} 23 | PACKAGE_VERSION_REVISION=${CLI_VERSION_REVISION:=1} 24 | 25 | BUILD_ARTIFACTSTAGINGDIRECTORY=${BUILD_ARTIFACTSTAGINGDIRECTORY:=${REPO_ROOT_DIR}/output/docker} 26 | IMAGE_NAME=microsoft/sqlcmd${BUILD_BUILDNUMBER:=''}:latest 27 | TAR_FILE=${BUILD_ARTIFACTSTAGINGDIRECTORY}/sqlcmd-docker-${PACKAGE_VERSION}-${PACKAGE_VERSION_REVISION}.tar 28 | 29 | echo "==========================================================" 30 | echo "PACKAGE_VERSION: ${PACKAGE_VERSION}" 31 | echo "PACKAGE_VERSION_REVISION: ${PACKAGE_VERSION_REVISION}" 32 | echo "BUILD_ARTIFACTSTAGINGDIRECTORY: ${BUILD_ARTIFACTSTAGINGDIRECTORY}" 33 | echo "Image name: ${IMAGE_NAME}" 34 | echo "Docker image file: ${TAR_FILE}" 35 | echo "==========================================================" 36 | 37 | docker load < ${TAR_FILE} 38 | docker run ${IMAGE_NAME} sqlcmd --help || exit 1 39 | -------------------------------------------------------------------------------- /release/linux/rpm/README.md: -------------------------------------------------------------------------------- 1 | RPM Release 2 | 3 | ## Building RPM in CI/CD pipeline 4 | 5 | Execute the following command from the root directory of this repository: 6 | 7 | ``` bash 8 | ./release/linux/rpm/pipeline.sh 9 | ``` 10 | Output will be sent to `./output/rpm` 11 | 12 | To test the packages: 13 | 14 | ``` bash 15 | ./release/linux/rpm/pipeline-test.sh 16 | ``` 17 | 18 | -------------------------------------------------------------------------------- /release/linux/rpm/build-rpm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #------------------------------------------------------------------------------ 4 | # Copyright (c) Microsoft Corporation. 5 | # Licensed under the MIT license. 6 | #------------------------------------------------------------------------------ 7 | 8 | # Description: 9 | # 10 | # Build a rpm `sqlcmd` package. This script is intended to be run in a 11 | # container with the respective distro/image laid down. 12 | # 13 | # Usage: 14 | # $ build-rpm.sh 15 | 16 | set -exv 17 | 18 | : "${CLI_VERSION:?CLI_VERSION environment variable not set.}" 19 | : "${CLI_VERSION_REVISION:?CLI_VERSION_REVISION environment variable not set.}" 20 | 21 | yum update -y 22 | yum install -y rpm-build 23 | 24 | export LC_ALL=en_US.UTF-8 25 | export REPO_ROOT_DIR=`cd $(dirname $0); cd ../../../; pwd` 26 | 27 | rpmbuild -v -bb --clean ${REPO_ROOT_DIR}/release/linux/rpm/sqlcmd.spec && cp /root/rpmbuild/RPMS/x86_64/* /mnt/output 28 | -------------------------------------------------------------------------------- /release/linux/rpm/pipeline.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | #------------------------------------------------------------------------------ 3 | # Copyright (c) Microsoft Corporation. 4 | # Licensed under the MIT license. 5 | #------------------------------------------------------------------------------ 6 | 7 | # Description: 8 | # 9 | # Instructions to be invoked under the build CI pipeline in AzureDevOps. 10 | # 11 | # Kickoff rpm package build. The build pipeline can then save it as an 12 | # artifact as it sees fit. 13 | # 14 | # Usage: 15 | # 16 | # foundation images: `centos:centos7|fedora:29` 17 | # 18 | # $ pipeline.sh 19 | 20 | set -exv 21 | 22 | : "${REPO_ROOT_DIR:=`cd $(dirname $0); cd ../../../; pwd`}" 23 | 24 | if [[ "${BUILD_OUTPUT}" != "" ]]; then 25 | cp ${BUILD_OUTPUT}/SqlcmdLinux-amd64/sqlcmd ${REPO_ROOT_DIR}/sqlcmd 26 | fi 27 | 28 | DIST_DIR=${BUILD_STAGINGDIRECTORY:=${REPO_ROOT_DIR}/output/rpm} 29 | DISTRO_BASE_IMAGE=( centos:centos7 fedora:29 ) 30 | 31 | CLI_VERSION=${CLI_VERSION:=0.0.1} 32 | 33 | echo "==========================================================" 34 | echo "CLI_VERSION: ${CLI_VERSION}" 35 | echo "CLI_VERSION_REVISION: ${CLI_VERSION_REVISION:=1}" 36 | echo "Distribution Image: ${DISTRO_BASE_IMAGE}" 37 | echo "Output location: ${DIST_DIR}" 38 | echo "==========================================================" 39 | 40 | mkdir -p ${DIST_DIR} || exit 1 41 | 42 | for i in ${!DISTRO_BASE_IMAGE[@]}; do 43 | image=${DISTRO_BASE_IMAGE[$i]} 44 | 45 | echo "==========================================================" 46 | echo "Build rpm on ${image}" 47 | echo "==========================================================" 48 | 49 | docker run --rm \ 50 | -v "${REPO_ROOT_DIR}":/mnt/repo \ 51 | -v "${DIST_DIR}":/mnt/output \ 52 | -v "${PIPELINE_WORKSPACE}":/mnt/workspace \ 53 | -e CLI_VERSION=${CLI_VERSION} \ 54 | -e CLI_VERSION_REVISION=${CLI_VERSION_REVISION:=1} \ 55 | "${image}" \ 56 | /mnt/repo/release/linux/rpm/build-rpm.sh 57 | done 58 | -------------------------------------------------------------------------------- /release/linux/rpm/sqlcmd.spec: -------------------------------------------------------------------------------- 1 | # RPM spec file for sqlcmd 2 | # Definition of macros used - https://fedoraproject.org/wiki/Packaging:RPMMacros?rd=Packaging/RPMMacros 3 | 4 | # .el7.centos -> .el7 5 | %if 0%{?rhel} 6 | %define dist .el%{?rhel} 7 | %endif 8 | 9 | %define name sqlcmd 10 | %define release 1%{?dist} 11 | %define version %{getenv:CLI_VERSION} 12 | %define repo_path %{getenv:REPO_ROOT_DIR} 13 | %define cli_lib_dir %{_libdir}/sqlcmd 14 | 15 | %undefine _missing_build_ids_terminate_build 16 | %global _missing_build_ids_terminate_build 0 17 | 18 | Summary: MSSQL SQLCMD CLI Tools 19 | License: https://github.com/microsoft/go-sqlcmd/blob/main/LICENSE 20 | Name: %{name} 21 | Version: %{version} 22 | Release: %{release} 23 | Url: https://github.com/microsoft/go-sqlcmd 24 | BuildArch: x86_64 25 | 26 | %description 27 | SQLCMD CLI, a multi-platform command line experience for Microsoft SQL Server and Azure SQL. 28 | 29 | %prep 30 | %install 31 | 32 | # Create executable 33 | mkdir -p %{buildroot}%{_bindir} 34 | cp %{repo_path}/sqlcmd %{buildroot}%{_bindir} 35 | 36 | %files 37 | %attr(0755,root,root) %{_bindir}/sqlcmd 38 | -------------------------------------------------------------------------------- /release/windows/choco/sqlcmd.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | sqlcmd 6 | 0.8.1 7 | sqlcmd (Install) 8 | Microsoft 9 | https://docs.microsoft.com/sql/tools/go-sqlcmd-utility 10 | https://github.com/microsoft/go-sqlcmd/blob/main/release/windows/msi/resources/go-sqlcmd_256.png 11 | Copyright (c) Microsoft Corporation 12 | https://github.com/microsoft/go-sqlcmd/blob/main/LICENSE 13 | false 14 | https://github.com/microsoft/go-sqlcmd 15 | https://github.com/microsoft/go-sqlcmd/tree/main/release/windows/choco 16 | https://docs.microsoft.com/sql/tools/go-sqlcmd-utility 17 | https://github.com/microsoft/go-sqlcmd/issues 18 | sqlcmd mssql sqlserver 19 | sqlcmd CLI for Microsoft SQL Server and Azure SQL 20 | sqlcmd is a multi-platform command line experience for Microsoft SQL Server and Azure SQL 21 | https://github.com/microsoft/go-sqlcmd/releases/tag/v0.8.1 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /release/windows/choco/tools/LICENSE.txt: -------------------------------------------------------------------------------- 1 | From: https://github.com/microsoft/go-sqlcmd/blob/main/LICENSE 2 | 3 | LICENSE 4 | 5 | MIT License 6 | 7 | Copyright (c) Microsoft Corporation. 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE 26 | -------------------------------------------------------------------------------- /release/windows/choco/tools/VERIFICATION.txt: -------------------------------------------------------------------------------- 1 | VERIFICATION 2 | Verification is intended to assist the Chocolatey moderators and community 3 | in verifying that this package's contents are trustworthy. 4 | 5 | Download .msi from Microsoft Download Center 6 | https://download.microsoft.com/download/d/4/4/d4403a51-2ab7-4ea8-b850-d2710c5e1323/sqlcmd_0.8.1-1.msi 7 | 8 | Run checksum -t sha256 -f sqlcmd_0.8.1-1.msi 9 | 10 | We are the software vendor (Microsoft) -------------------------------------------------------------------------------- /release/windows/choco/tools/chocolateyinstall.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = 'Stop'; 2 | 3 | $toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" 4 | $url = '{{DownloadUrl}}' 5 | $url64 = 'https://download.microsoft.com/download/d/4/4/d4403a51-2ab7-4ea8-b850-d2710c5e1323/sqlcmd_0.8.1-1.msi' 6 | 7 | $packageArgs = @{ 8 | packageName = $env:ChocolateyPackageName 9 | unzipLocation = $toolsDir 10 | fileType = 'MSI' 11 | url = $url 12 | url64bit = $url64 13 | 14 | softwareName = 'sqlcmd*' 15 | 16 | checksum = '{{Checksum}}' 17 | checksumType = '{{ChecksumType}}' 18 | checksum64 = '03587762932D5A66ACFE15D306FE14645D53BC61162B4DA0D9AF29B4A8A1550D' 19 | checksumType64= 'sha256' 20 | 21 | silentArgs = "/qn /norestart /l*v `"$($env:TEMP)\$($packageName).$($env:chocolateyPackageVersion).MsiInstall.log`"" 22 | validExitCodes= @(0, 3010, 1641) 23 | } 24 | 25 | Install-ChocolateyPackage @packageArgs 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /release/windows/msi/README.md: -------------------------------------------------------------------------------- 1 | # Windows MSI 2 | 3 | This document provides instructions on creating the MSI. 4 | 5 | ## Prerequisites 6 | 7 | 1. WIX Toolset 8 | 2. Turn on the '.NET Framework 3.5' Windows Feature (required for WIX Toolset) 9 | 3. Install [WIX Toolset build tools](http://wixtoolset.org/releases/) if not already installed 10 | 4. Install [Microsoft Build Tools](https://www.microsoft.com/download/details.aspx?id=48159) 11 | 12 | ## Building 13 | 14 | 1. Set the `CLI_VERSION` environment variable 15 | 2. Run `release\windows\msi\scripts\pipeline.cmd` 16 | 3. The unsigned MSI will be in the `.\output\msi` folder 17 | 18 | > **Note:** For `building step 1.` above set both env-vars to the same version-tag for the immediate, this will consolidated in the future. 19 | 20 | ## Release Install/Update/Uninstall Steps 21 | 22 | > **Note:** Replace `{{HOST}}` and `{{CLI_VERSION}}` with the appropriate values. 23 | 24 | ### Install `Sqlcmd Tools` on Windows 25 | 26 | The MSI distributable is used for installing or updating the `Sqlcmd Tools` CLI on Windows. 27 | 28 | [Download the MSI Installer](http://{{HOST}}/sqlcmd-{{CLI_VERSION}}.msi) 29 | 30 | When the installer asks if it can make changes to your computer, click the `Yes` box. 31 | 32 | ### Uninstall 33 | 34 | You can uninstall the `SqlCmd Tools` from the Windows _Apps and Features_ list. To uninstall: 35 | 36 | | Platform | Instructions | 37 | | ------------- |--------------------------------------------------------| 38 | | Windows 10 | Start > Settings > Apps | 39 | | Windows 8 | Start > Control Panel > Programs > Uninstall a program | 40 | 41 | The program to uninstall is listed as **Sqlcmd Tools** . Select this application, then click the `Uninstall` button. 42 | -------------------------------------------------------------------------------- /release/windows/msi/resources/CLI_LICENSE.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033\deflangfe1033{\fonttbl{\f0\fswiss\fprq2\fcharset0 Calibri;}} 2 | {\*\generator Riched20 10.0.19041}{\*\mmathPr\mnaryLim0\mdispDef1\mwrapIndent1440 }\viewkind4\uc1 3 | \pard\widctlpar\sa160\sl252\slmult1\f0\fs22 MIT License\par 4 | Copyright (c) Microsoft Corporation.\par 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\par 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\par 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE\par 8 | } 9 | -------------------------------------------------------------------------------- /release/windows/msi/resources/banner.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/go-sqlcmd/e8f9c267602cf2949f6b3ba2bfd7a47c891b7479/release/windows/msi/resources/banner.bmp -------------------------------------------------------------------------------- /release/windows/msi/resources/dialog.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/go-sqlcmd/e8f9c267602cf2949f6b3ba2bfd7a47c891b7479/release/windows/msi/resources/dialog.bmp -------------------------------------------------------------------------------- /release/windows/msi/resources/go-sqlcmd_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/go-sqlcmd/e8f9c267602cf2949f6b3ba2bfd7a47c891b7479/release/windows/msi/resources/go-sqlcmd_256.png -------------------------------------------------------------------------------- /release/windows/msi/resources/sqlcmd.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/go-sqlcmd/e8f9c267602cf2949f6b3ba2bfd7a47c891b7479/release/windows/msi/resources/sqlcmd.ico -------------------------------------------------------------------------------- /release/windows/msi/scripts/pipeline-test.ps1: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) Microsoft Corporation. All rights reserved. 3 | #------------------------------------------------------------------------------ 4 | 5 | # Description: 6 | # 7 | # Instructions to be invoked under the build CI pipeline in AzureDevOps. 8 | # 9 | # Kickoff MSI package install test. 10 | # 11 | # Usage: 12 | # 13 | # set SYSTEM_ARTIFACTSDIRECTORY=\path\to\msi\sqlcmd-.msi 14 | # 15 | # $ pipeline-test.ps1 16 | 17 | if (-not (Test-Path env:CLI_VERSION)) { $env:CLI_VERSION = '0.0.1' } 18 | if (-not (Test-Path env:CLI_VERSION_REVISION)) { $env:CLI_VERSION_REVISION = '1' } 19 | if (-not (Test-Path env:ARCHITECTURE)) { $env:ARCHITECTURE = 'amd64' } 20 | 21 | tree /A /F $env:SYSTEM_ARTIFACTSDIRECTORY 22 | 23 | $msiPath = Join-Path $env:SYSTEM_ARTIFACTSDIRECTORY ("sqlcmd-" + $env:ARCHITECTURE + "_" + $env:CLI_VERSION + "-" + $env:CLI_VERSION_REVISION + ".msi") 24 | 25 | $msiPath 26 | 27 | $InstallArgs = @( 28 | "/I" 29 | $msiPath 30 | "/norestart" 31 | "/L*v" 32 | ".\install-logs.txt" 33 | "/qn" 34 | ) 35 | 36 | Write-Output "Starting msi install $msiPath..." 37 | Start-Process "msiexec.exe" -ArgumentList $InstallArgs -Wait -NoNewWindow 38 | Get-Content .\install-logs.txt 39 | 40 | Write-Output "Done installing msi, checking PATH setup..." 41 | Write-Output "$env:path" 42 | $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") 43 | Write-Output "$env:path" 44 | 45 | $sqlcmd = $env:ProgramFiles + "\SqlCmd\sqlcmd" 46 | & $sqlcmd --help 47 | -------------------------------------------------------------------------------- /release/windows/msi/sqlcmd.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31729.503 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "sqlcmd", "sqlcmd.wixproj", "{10101119-735C-48F9-A3FD-B3362A78BA8A}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {10101119-735C-48F9-A3FD-B3362A78BA8A}.Debug|x64.ActiveCfg = Debug|x64 17 | {10101119-735C-48F9-A3FD-B3362A78BA8A}.Debug|x64.Build.0 = Debug|x64 18 | {10101119-735C-48F9-A3FD-B3362A78BA8A}.Debug|x86.ActiveCfg = Debug|x86 19 | {10101119-735C-48F9-A3FD-B3362A78BA8A}.Debug|x86.Build.0 = Debug|x86 20 | {10101119-735C-48F9-A3FD-B3362A78BA8A}.Release|x64.ActiveCfg = Release|x64 21 | {10101119-735C-48F9-A3FD-B3362A78BA8A}.Release|x64.Build.0 = Release|x64 22 | {10101119-735C-48F9-A3FD-B3362A78BA8A}.Release|x86.ActiveCfg = Release|x86 23 | {10101119-735C-48F9-A3FD-B3362A78BA8A}.Release|x86.Build.0 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {D9594663-3E11-4507-AEEB-AB2714988330} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /testdata/sql.txt: -------------------------------------------------------------------------------- 1 | select 1 as col1 2 | go 3 | 4 | --------------------------------------------------------------------------------