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