Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,24 @@ sqlcmd

If no current context exists, `sqlcmd` (with no connection parameters) reverts to the original ODBC `sqlcmd` behavior of creating an interactive session to the default local instance on port 1433 using trusted authentication, otherwise it will create an interactive session to the current context.

### Piping input to sqlcmd

You can pipe SQL commands directly to `sqlcmd` from the command line. This is useful for scripting and automation:

**PowerShell:**
```powershell
"SELECT @@version" | sqlcmd -S myserver -d mydb -G
"SELECT name FROM sys.databases" | sqlcmd -S myserver.database.windows.net -d mydb -G
```

**Bash:**
```bash
echo "SELECT @@version" | sqlcmd -S myserver -d mydb -G
cat myscript.sql | sqlcmd -S myserver -d mydb -G
```

Note: When piping input, `GO` batch terminators are optional—`sqlcmd` will automatically execute the batch when the input ends. However, you can still include `GO` statements if you want to execute multiple batches.

## Sqlcmd

The `sqlcmd` project aims to be a complete port of the original ODBC sqlcmd to the `Go` language, utilizing the [go-mssqldb][] driver. For full documentation of the tool and installation instructions, see [go-sqlcmd-utility][].
Expand Down
4 changes: 4 additions & 0 deletions cmd/sqlcmd/sqlcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,10 @@ func isConsoleInitializationRequired(connect *sqlcmd.ConnectSettings, args *SQLC
} else if iactive {
// Interactive mode also requires console
needsConsole = true
} else if isStdinRedirected && args.InputFile == nil && args.Query == "" && len(args.ChangePasswordAndExit) == 0 {
// Stdin is redirected (piped input) and no input file or query specified
// We need a console to read from the redirected stdin (fixes #607)
needsConsole = true
}

return needsConsole, iactive
Expand Down
47 changes: 45 additions & 2 deletions cmd/sqlcmd/stdin_console_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ func TestIsConsoleInitializationRequiredWithRedirectedStdin(t *testing.T) {
// Now test with no authentication (no password required)
connectConfig = sqlcmd.ConnectSettings{}
needsConsole, isInteractive = isConsoleInitializationRequired(&connectConfig, args)
// Should not need console and not be interactive
assert.False(t, needsConsole, "Console should not be needed with redirected stdin and no password")
// Should need console (for reading redirected stdin) but not be interactive (fixes #607)
assert.True(t, needsConsole, "Console should be needed with redirected stdin to read piped input")
assert.False(t, isInteractive, "Should not be interactive mode with redirected stdin")

// Test with direct terminal input (simulated by restoring original stdin)
Expand All @@ -78,3 +78,46 @@ func TestIsConsoleInitializationRequiredWithRedirectedStdin(t *testing.T) {
assert.Equal(t, args.InputFile == nil && args.Query == "" && len(args.ChangePasswordAndExit) == 0, isInteractive,
"Interactive mode should be true with terminal stdin and no input files or queries")
}

// TestPipedInputRequiresConsole tests that piped stdin input correctly requires
// console initialization to prevent nil pointer dereference (fixes #607)
func TestPipedInputRequiresConsole(t *testing.T) {
// Save original stdin
originalStdin := os.Stdin
defer func() { os.Stdin = originalStdin }()

// Create a pipe to simulate piped input like: echo "select 1" | sqlcmd
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("Failed to create pipe: %v", err)
}
defer r.Close()
defer w.Close()

// Replace stdin with our pipe reader
os.Stdin = r

// Write some SQL to the pipe (simulating: echo "select 1" | sqlcmd)
go func() {
_, _ = w.WriteString("SELECT @@SERVERNAME\nGO\n")
w.Close()
}()

// Test with no authentication required (simulates -G flag with Azure AD)
connectConfig := sqlcmd.ConnectSettings{}
args := &SQLCmdArguments{} // No InputFile, no Query - relies on stdin

needsConsole, isInteractive := isConsoleInitializationRequired(&connectConfig, args)

// With piped input, we should need a console to read from stdin
// but should not be in interactive mode
assert.True(t, needsConsole, "Console should be required for piped stdin input to avoid nil pointer dereference")
assert.False(t, isInteractive, "Piped input should not be considered interactive mode")

// Test that ChangePasswordAndExit bypasses the piped input console requirement
// since no stdin reading is needed for password change operations
args.ChangePasswordAndExit = "newpassword"
needsConsole, isInteractive = isConsoleInitializationRequired(&connectConfig, args)
assert.False(t, needsConsole, "Console should not be required when ChangePasswordAndExit is set")
assert.False(t, isInteractive, "Should not be interactive mode with ChangePasswordAndExit")
}
Loading