Skip to content

Fix piping query without go command into sqlcmd-go #583

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 2, 2025
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
28 changes: 28 additions & 0 deletions cmd/sqlcmd/pipe_detection_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

package sqlcmd

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func TestStdinPipeDetection(t *testing.T) {
// Get stdin info
fi, err := os.Stdin.Stat()
assert.NoError(t, err, "os.Stdin.Stat()")

// On most CI systems, stdin will be a pipe or file (not a terminal)
// We're testing the logic, not expecting a specific result
isPipe := false
if fi != nil && (fi.Mode()&os.ModeCharDevice) == 0 {
isPipe = true
}

// Just making sure the detection code doesn't crash
// The actual value will depend on the environment
t.Logf("Stdin detected as pipe: %v", isPipe)
}
34 changes: 22 additions & 12 deletions cmd/sqlcmd/sqlcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -714,25 +714,31 @@ func setConnect(connect *sqlcmd.ConnectSettings, args *SQLCmdArguments, vars *sq
}
}

func isConsoleInitializationRequired(connect *sqlcmd.ConnectSettings, args *SQLCmdArguments) bool {
// Password input always requires console initialization
if connect.RequiresPassword() {
return true
}
func isConsoleInitializationRequired(connect *sqlcmd.ConnectSettings, args *SQLCmdArguments) (bool, bool) {
needsConsole := false

// Check if stdin is from a terminal or a redirection
isStdinRedirected := false
file, err := os.Stdin.Stat()
if err == nil {
// If stdin is not a character device, it's coming from a pipe or redirect
if (file.Mode() & os.ModeCharDevice) == 0 {
// Non-interactive: stdin is redirected
return false
isStdinRedirected = true
}
}

// If we get here, stdin is from a terminal or we couldn't determine
iactive := args.InputFile == nil && args.Query == "" && len(args.ChangePasswordAndExit) == 0
return iactive
// Determine if we're in interactive mode
iactive := args.InputFile == nil && args.Query == "" && len(args.ChangePasswordAndExit) == 0 && !isStdinRedirected

// Password input always requires console initialization
if connect.RequiresPassword() {
needsConsole = true
} else if iactive {
// Interactive mode also requires console
needsConsole = true
}

return needsConsole, iactive
}

func run(vars *sqlcmd.Variables, args *SQLCmdArguments) (int, error) {
Expand All @@ -744,7 +750,8 @@ func run(vars *sqlcmd.Variables, args *SQLCmdArguments) (int, error) {
var connectConfig sqlcmd.ConnectSettings
setConnect(&connectConfig, args, vars)
var line sqlcmd.Console = nil
if isConsoleInitializationRequired(&connectConfig, args) {
needsConsole, isInteractive := isConsoleInitializationRequired(&connectConfig, args)
if needsConsole {
line = console.NewConsole("")
defer line.Close()
}
Expand Down Expand Up @@ -835,7 +842,10 @@ func run(vars *sqlcmd.Variables, args *SQLCmdArguments) (int, error) {
}
iactive := args.InputFile == nil && args.Query == ""
if iactive || s.Query != "" {
err = s.Run(once, false)
// If we're not in interactive mode and stdin is redirected,
// we want to process all input without requiring GO statements
processAll := !isInteractive
err = s.Run(once, processAll)
} else {
for f := range args.InputFile {
if err = s.IncludeFile(args.InputFile[f], true); err != nil {
Expand Down
3 changes: 2 additions & 1 deletion cmd/sqlcmd/sqlcmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,8 @@ func TestConditionsForPasswordPrompt(t *testing.T) {
setConnect(&connectConfig, &args, vars)
connectConfig.AuthenticationMethod = testcase.authenticationMethod
connectConfig.Password = testcase.pwd
assert.Equal(t, testcase.expectedResult, isConsoleInitializationRequired(&connectConfig, &args), "Unexpected test result encountered for console initialization")
needsConsole, _ := isConsoleInitializationRequired(&connectConfig, &args)
assert.Equal(t, testcase.expectedResult, needsConsole, "Unexpected test result encountered for console initialization")
assert.Equal(t, testcase.expectedResult, connectConfig.RequiresPassword() && connectConfig.Password == "", "Unexpected test result encountered for password prompt conditions")
}
}
Expand Down
24 changes: 18 additions & 6 deletions cmd/sqlcmd/stdin_console_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,25 @@ func TestIsConsoleInitializationRequiredWithRedirectedStdin(t *testing.T) {
t.Logf("RequiresPassword() returns: %v", connectConfig.RequiresPassword())

// Test with SQL authentication that requires a password
res := isConsoleInitializationRequired(&connectConfig, args)
// Should be true since password is required, even with redirected stdin
assert.True(t, res, "Console initialization should be required when SQL authentication is used")
needsConsole, isInteractive := isConsoleInitializationRequired(&connectConfig, args)
// Should need console since password is required, but not be interactive
assert.True(t, needsConsole, "Console should be needed when SQL authentication is used")
assert.False(t, isInteractive, "Should not be interactive mode with redirected stdin")

// Now test with no authentication (no password required)
connectConfig = sqlcmd.ConnectSettings{}
res = isConsoleInitializationRequired(&connectConfig, args)
// Should be false since stdin is redirected and no password is required
assert.False(t, res, "Console initialization should not be required with redirected stdin and no password")
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")
assert.False(t, isInteractive, "Should not be interactive mode with redirected stdin")

// Test with direct terminal input (simulated by restoring original stdin)
os.Stdin = originalStdin
connectConfig = sqlcmd.ConnectSettings{} // No password needed
needsConsole, isInteractive = isConsoleInitializationRequired(&connectConfig, args)
// If no input file or query is specified, it should be interactive mode
assert.Equal(t, args.InputFile == nil && args.Query == "" && len(args.ChangePasswordAndExit) == 0, needsConsole,
"Console needs should match interactive mode requirements with terminal stdin")
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")
}
Loading