diff --git a/cmd/modern/e2e_test.go b/cmd/modern/e2e_test.go index 9c3d14e6..eacc33c2 100644 --- a/cmd/modern/e2e_test.go +++ b/cmd/modern/e2e_test.go @@ -12,6 +12,7 @@ import ( "strings" "sync" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -312,6 +313,22 @@ func TestE2E_PipedInput_WithBytesBuffer_NoPanic(t *testing.T) { assert.NotContains(t, outputStr, "nil pointer", "should not have nil pointer error") } +func TestE2E_QueryTimeout_NoHang(t *testing.T) { + skipIfNoLiveConnection(t) + binary := buildBinary(t) + + args := append([]string{"-C", "-t", "1", "-Q", "WAITFOR DELAY '00:00:10'"}, getAuthArgs(t)...) + cmd := exec.Command(binary, args...) + cmd.Env = os.Environ() + + start := time.Now() + output, _ := cmd.CombinedOutput() + elapsed := time.Since(start) + + assert.Contains(t, string(output), "Timeout expired") + assert.Less(t, elapsed, 30*time.Second, "command hung instead of timing out") +} + // cleanupBinary removes the temporary build directory containing the test binary. // TestMain calls this to ensure deterministic cleanup instead of relying on // eventual OS temp directory maintenance. diff --git a/pkg/sqlcmd/sqlcmd.go b/pkg/sqlcmd/sqlcmd.go index 5e572a94..a9e9418c 100644 --- a/pkg/sqlcmd/sqlcmd.go +++ b/pkg/sqlcmd/sqlcmd.go @@ -431,6 +431,9 @@ func (s *Sqlcmd) runQuery(query string) (int, error) { } retmsg := &sqlexp.ReturnMessage{} rows, qe := s.db.QueryContext(ctx, query, retmsg) + if rows != nil { + defer func() { _ = rows.Close() }() + } if qe != nil { s.Format.AddError(qe) }