Skip to content
Open
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
36 changes: 34 additions & 2 deletions pkg/sqlcmd/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"io"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -59,6 +60,11 @@ const (
ControlReplaceConsecutive
)

const (
realDefaultWidth int64 = 14 // REAL and SMALLMONEY
floatDefaultWidth int64 = 24 // FLOAT and MONEY
)

type columnDetail struct {
displayWidth int64
leftJustify bool
Expand Down Expand Up @@ -371,11 +377,11 @@ func calcColumnDetails(cols []*sql.ColumnType, fixed int64, variable int64) ([]c
columnDetails[i].displayWidth = max64(21, nameLen)
case "REAL", "SMALLMONEY":
columnDetails[i].leftJustify = false
columnDetails[i].displayWidth = max64(14, nameLen)
columnDetails[i].displayWidth = max64(realDefaultWidth, nameLen)
columnDetails[i].zeroesAfterDecimal = true
case "FLOAT", "MONEY":
columnDetails[i].leftJustify = false
columnDetails[i].displayWidth = max64(24, nameLen)
columnDetails[i].displayWidth = max64(floatDefaultWidth, nameLen)
columnDetails[i].zeroesAfterDecimal = true
case "DECIMAL":
columnDetails[i].leftJustify = false
Expand Down Expand Up @@ -530,6 +536,10 @@ func (f *sqlCmdFormatterType) scanRow(rows *sql.Rows) ([]string, error) {
} else {
row[n] = "0"
}
case float64:
row[n] = formatFloat(x, 64, f.columnDetails[n])
case float32:
row[n] = formatFloat(float64(x), 32, f.columnDetails[n])
default:
var err error
if row[n], err = fmt.Sprintf("%v", x), nil; err != nil {
Expand All @@ -552,6 +562,28 @@ func dateTimeFormatString(scale int, addOffset bool) string {
return format
}

// formatFloat formats a float value to match ODBC sqlcmd behavior.
// Uses decimal notation for typical values, falls back to scientific for extreme values.
func formatFloat(x float64, bitSize int, col columnDetail) string {
formatted := strconv.FormatFloat(x, 'f', -1, bitSize)

// Determine width threshold for fallback to scientific notation
threshold := col.displayWidth
if threshold == 0 {
typeName := col.col.DatabaseTypeName()
if typeName == "REAL" || typeName == "SMALLMONEY" {
threshold = realDefaultWidth
} else {
threshold = floatDefaultWidth
}
}

if int64(len(formatted)) > threshold {
formatted = strconv.FormatFloat(x, 'g', -1, bitSize)
}
return formatted
}

// Prints the final version of a cell based on formatting variables and command line parameters
func (f *sqlCmdFormatterType) printColumnValue(val string, col int) {
c := f.columnDetails[col]
Expand Down
27 changes: 27 additions & 0 deletions pkg/sqlcmd/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,30 @@ func TestFormatterXmlMode(t *testing.T) {
assert.NoError(t, err, "runSqlCmd returned error")
assert.Equal(t, `<sys.databases name="master"/>`+SqlcmdEol, buf.buf.String())
}

func TestFormatterFloatDecimalNotation(t *testing.T) {
s, buf := setupSqlCmdWithMemoryOutput(t)
defer func() { _ = buf.Close() }()

s.vars.Set(SQLCMDMAXVARTYPEWIDTH, "256")
query := `SELECT CAST(4713347.3103808956 AS FLOAT) as val`
err := runSqlCmd(t, s, []string{query, "GO"})
assert.NoError(t, err)

output := buf.buf.String()
assert.NotContains(t, output, "e+", "typical floats should use decimal notation")
assert.Contains(t, output, "4713347.310380", "should contain decimal value")
}

func TestFormatterFloatScientificFallback(t *testing.T) {
s, buf := setupSqlCmdWithMemoryOutput(t)
defer func() { _ = buf.Close() }()

s.vars.Set(SQLCMDMAXVARTYPEWIDTH, "256")
query := `SELECT CAST(1e100 AS FLOAT) as val`
err := runSqlCmd(t, s, []string{query, "GO"})
assert.NoError(t, err)

output := buf.buf.String()
assert.Contains(t, output, "e+", "extreme values should use scientific notation")
}
Loading