Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 31, 2026

Float Formatting Fix - Complete ✅

This PR fixes the inconsistent float formatting between go-sqlcmd and ODBC SQLCMD versions.

Problem

Previously, go-sqlcmd would output floats using scientific notation (e.g., 4.713347310380896e+06) while ODBC SQLCMD uses decimal notation (e.g., 4713347.3103808956), causing compatibility issues.

Solution

Implemented a hybrid approach that:

  1. Prefers decimal notation ('f' format) for typical values to match ODBC behavior
  2. Falls back to scientific notation ('g' format) when decimal formatting would exceed the column display width (24 chars for FLOAT, 14 for REAL)
  3. Prevents truncation issues for extreme values like 1e+308 or 1e-308
  4. Uses DatabaseTypeName() to determine correct bitSize (32 for REAL/SMALLMONEY, 64 for FLOAT/MONEY)
  5. Applies fallback even when displayWidth=0 using type default widths to prevent unbounded output
  6. Centralizes default width constants to prevent drift between calcColumnDetails and scanRow

Changes

  • Modified pkg/sqlcmd/format.go to use hybrid float formatting for both FLOAT (float64) and REAL (float32)
  • Fixed float formatting to key off DatabaseTypeName() for proper bitSize determination
  • Centralized float type default widths as constants (realDefaultWidth=14, floatDefaultWidth=24)
  • Improved fallback logic to handle displayWidth=0 case using type default widths
  • Added comprehensive tests including extreme value test cases
  • Added test for displayWidth=0 (unlimited width) scenario
  • Fixed test configuration to properly validate fallback behavior with correct SQLCMDMAXVARTYPEWIDTH settings
  • Added test coverage for REAL (float32) columns with improved assertions
  • Fixed column parsing to use correct separator (whitespace, not semicolon)
  • Enhanced test to verify both positive and negative exponent handling
  • Fixed connectCommand validation to prevent panic on empty parameters
  • Removed dead code from connectCommand
  • Fixed editCommand file handle cleanup to work on all error paths
  • Fixed import grouping in console_redirect.go
  • Fixed all golangci-lint errcheck violations in commands.go
  • Removed trailing whitespace in commands.go
  • Updated .gitignore to exclude build artifacts
  • Fixed errcheck linting errors in test functions
  • All existing tests pass
  • All code review feedback addressed
  • CodeQL security scan passed with no vulnerabilities
  • Code formatting cleaned up
  • golangci-lint passes with 0 issues

Behavior

  • Typical values (from issue): Use decimal notation ✓
    • 4713347.310380896 instead of 4.713347310380896e+06
  • Extreme values: Use scientific notation to avoid misleading truncation ✓
    • 1e+308 instead of truncated 100000000000000000000000
    • 1e-308 instead of truncated 0.0000000000000000000000
  • REAL vs FLOAT: Correctly uses 32-bit precision for REAL/SMALLMONEY, 64-bit for FLOAT/MONEY ✓
  • Handles displayWidth=0: Uses centralized type default widths for fallback threshold ✓
  • File handle cleanup: Ensures file is closed on all paths (Windows compatibility) ✓

Testing

  • Original issue values display correctly in decimal notation
  • Extreme values (both large and small) fall back to scientific notation appropriately
  • Extreme values with unlimited width (displayWidth=0) still use scientific notation
  • REAL (float32) columns work correctly with proper display width and precision
  • Tests properly configured to validate formatting under normal column-width settings
  • Tests use correct column separator for parsing output
  • All non-database tests pass successfully
  • Code compiles without errors or warnings
  • All linting checks pass (0 issues)
Original prompt

This section details on the original issue you should resolve

<issue_title>Inconsistent Float Formatting Between go-sqlcmd and ODBC SQLCMD Versions</issue_title>
<issue_description>Description
In Windows OS the go-sqlcmd application produces a different output format for FLOAT data types compared to the ODBC-based SQLCMD.EXE when executing the same SQL query. This discrepancy can cause issues for applications and scripts that rely on consistent float formatting.

Steps to Reproduce

  1. Create the following table:
CREATE TABLE [dbo].[Point](
   [PointId] [int] NOT NULL,
   [SessionId] [int] NULL,
   [Longitude] [float] NOT NULL,
   [Latitude] [float] NOT NULL,
CONSTRAINT [PK_Point] PRIMARY KEY CLUSTERED 
(
    [PointId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
) ON [PRIMARY]
GO

INSERT INTO [dbo].[Point] ([PointId], [SessionId], [Longitude], [Latitude]) VALUES 
  (1, 3, 788991.19988463481, 4713347.3103808956),
  (2, 3, 789288.40771771886, 4712632.075629076),
  (3, 3, 788569.36558582436, 4714608.0418091472);
GO
  1. Execute the following query using go-sqlcmd (version 1.8.1):
sqlCmd.exe -S serverName -d dbname -U username -P password -Q "SELECT TOP 10 * FROM dbo.Point" -s";" -W
  1. Execute the same query using the ODBC SQLCMD (version 15.0.4410):
sqlCmd.exe -S serverName -d dbname -U username -P password -Q "SELECT TOP 10 * FROM dbo.Point" -s";" -W

Observed Behavior

go-sqlcmd Output:

PointId;SessionId;Longitude;Latitude
-------;---------;---------;--------
1;3;788991.1998846348;4.713347310380896e+06
2;3;789288.4077177189;4.712632075629076e+06
3;3;788569.3655858244;4.714608041809147e+06

ODBC SQLCMD Output:

PointId;SessionId;Longitude;Latitude
-------;---------;---------;--------
1;3;788991.19988463481;4713347.3103808956
2;3;789288.40771771886;4712632.075629076
3;3;788569.36558582436;4714608.0418091472

Expected Behavior

The float formatting in the output of go-sqlcmd should align with the behavior of the ODBC SQLCMD to maintain compatibility and consistency across different SQLCMD implementations.

Environment

  • go-sqlcmd version: 1.8.1
  • ODBC sqlcmd version: 15.0.4410
  • Database Server: SQL Server 2019, SQL Server 2022
  • Operating System: Windows 11, Windows server

Additional Information

This issue primarily affects the formatting of float numbers. For example:

  • In go-sqlcmd, 4.713347310380896e+06
  • In ODBC sqlcmd, 4713347.3103808956

The scientific notation output from go-sqlcmd is less intuitive and might require additional parsing in downstream applications.

Request

Please investigate this behavior and consider aligning the float formatting of go-sqlcmd with the output of the ODBC-based SQLCMD.EXE.

</issue_description>

Comments on the Issue (you are @copilot in this section)

@shueybubbles How were the values inserted? Is go-sqlcmd reading from the same table instance as ODBC sqlcmd? @shueybubbles The ODBC logic for formatting floats is pretty exotic. I'll see how close various standard options available in `strconv.FormatFloat` get me to the desired behavior. @shueybubbles FWIW I think the "best" way to query floats to get a consistent format is to use the `STR` function in TSQL to make sure your query result renders in a driver-independent and app-independent way. Does SSMS format floats the same way ODBC does? @shueybubbles Go and SQL floats may have some precision mismatch issues (I am far from a floating point expert).

I wrote this experiment:

package main

import (
	"fmt"
	"strconv"
)

func main() {

	f, _ := strconv.ParseFloat("4714608.0418091472", 64)
	fs := strconv.FormatFloat(f, 'f', -1, 64)
	fmt.Println(fs)
}

The output drops the last digit:

4714608.041809147

Also, there are situations where ODBC converts to exponent, but I don't quite understand the logic enough to write test cases to verify any change I make will align with ODBC.

In short, I don't have straightforward way to make our rendering of SQL floats match the ODBC rendering in every instance. I don't know if your example is a frequent problem to justify spending a lot of time on it.</comment_new>
<comment_new>@shueybubbles
thx I'll look at big.Float since we already use math/big elsewhere.
Using f isn't quite right, though, because ODBC uses some other special sauce to decide when to use exponents instead of just a decimal. We could flip it so there are cases where ODBC is using exponents and go-sqlcmd does not.

As I mentioned above - any query that needs to return float-as-string ...


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 3 commits January 31, 2026 19:59
Co-authored-by: dlevy-msft-sql <194277063+dlevy-msft-sql@users.noreply.github.com>
Co-authored-by: dlevy-msft-sql <194277063+dlevy-msft-sql@users.noreply.github.com>
Co-authored-by: dlevy-msft-sql <194277063+dlevy-msft-sql@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix inconsistent float formatting in go-sqlcmd Fix float formatting to match ODBC sqlcmd (use decimal notation instead of scientific) Jan 31, 2026
Copilot AI requested a review from dlevy-msft-sql January 31, 2026 20:04
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates go-sqlcmd’s result formatting so SQL Server FLOAT/REAL values are rendered in decimal notation (rather than scientific notation) to better match the legacy ODBC sqlcmd output and improve script compatibility.

Changes:

  • Add explicit float64/float32 handling in scanRow() using strconv.FormatFloat(..., 'f', -1, ...) to avoid scientific notation.
  • Add a formatter test that asserts float output does not include scientific notation and includes expected decimal substrings.
  • Ignore the root-level modern build artifact in .gitignore.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.

File Description
pkg/sqlcmd/format.go Forces decimal float formatting during row scanning to align with ODBC sqlcmd output style.
pkg/sqlcmd/format_test.go Adds an integration-style test covering float output formatting expectations.
.gitignore Ignores a root-level modern binary/artifact.

@dlevy-msft-sql dlevy-msft-sql added bug Something isn't working Size: S Small issue (less than one week effort) needs-work labels Feb 1, 2026
Copilot AI and others added 2 commits February 1, 2026 03:52
…extreme values

Co-authored-by: dlevy-msft-sql <194277063+dlevy-msft-sql@users.noreply.github.com>
Co-authored-by: dlevy-msft-sql <194277063+dlevy-msft-sql@users.noreply.github.com>
Copilot AI changed the title Fix float formatting to match ODBC sqlcmd (use decimal notation instead of scientific) Fix float formatting to match ODBC sqlcmd with hybrid approach (decimal notation with scientific fallback) Feb 1, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 3 changed files in this pull request and generated 2 comments.

@dlevy-msft-sql
Copy link
Contributor

@copilot fix the issues identified in the last review

Copilot AI and others added 2 commits February 1, 2026 17:48
- Set SQLCMDMAXVARTYPEWIDTH in TestFormatterFloatFormattingExtremeValues to enable fallback behavior testing
- Add TestFormatterRealFormatting to test float32 (REAL) column formatting with both typical and extreme values

Co-authored-by: dlevy-msft-sql <194277063+dlevy-msft-sql@users.noreply.github.com>
- Add more precise assertions to verify typical values don't use scientific notation
- Split output by lines to examine data separately from headers
- Verify the typical value column doesn't contain 'e' notation

Co-authored-by: dlevy-msft-sql <194277063+dlevy-msft-sql@users.noreply.github.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 10 changed files in this pull request and generated no new comments.

@dlevy-msft-sql
Copy link
Contributor

@copilot Fix the failing GitHub Actions job golangci-lint (run: https://github.com/microsoft/go-sqlcmd/actions/runs/21575514192/job/62162530634?pr=686) in microsoft/go-sqlcmd at ref 5194b35.

The job fails due to errcheck violations in pkg/sqlcmd/commands.go:

At listVarCommand around line ~411, the return value of fmt.Fprintf is not checked.
Update the code to check the returned error and return it from listVarCommand if non-nil.
Example pattern:
Go
if _, err := fmt.Fprintf(w, ..., ...); err != nil {
return err
}
In editCommand around line ~546, the deferred os.Remove(fileName) return value is not checked.
Wrap the deferred remove in an anonymous function and handle the error (either ignore intentionally with a comment or optionally log to the error writer). Ensure errcheck is satisfied.
Example pattern:
Go
defer func() {
if err := os.Remove(fileName); err != nil {
// intentionally ignore / best-effort cleanup
}
}()
In editCommand around line ~555, the file.Close() return value is not checked.
Change it to:
Go
if err := file.Close(); err != nil {
return err
}
Make the minimal changes needed to satisfy golangci-lint with only-new-issues: true, keep behavior the same, and ensure the file still compiles and tests pass.

- Check fmt.Fprintf return value in listVarCommand
- Wrap deferred os.Remove in anonymous function with error handling
- Check file.Close return value in editCommand
- All 3 errcheck violations resolved

Co-authored-by: dlevy-msft-sql <194277063+dlevy-msft-sql@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 2, 2026

@copilot Fix the failing GitHub Actions job golangci-lint (run: https://github.com/microsoft/go-sqlcmd/actions/runs/21575514192/job/62162530634?pr=686) in microsoft/go-sqlcmd at ref 5194b35...

Fixed in commit 85c8c8b. All 3 golangci-lint errcheck violations resolved:

  1. listVarCommand (line 411): Now checks fmt.Fprintf return value and returns error if non-nil
  2. editCommand (line 546): Wrapped deferred os.Remove in anonymous function with error handling
  3. editCommand (line 555): Now checks file.Close return value and returns error if non-nil

golangci-lint now passes with 0 issues.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 10 changed files in this pull request and generated 2 comments.

@dlevy-msft-sql
Copy link
Contributor

@copilot fix the code review issues

- Add deferred file.Close in editCommand to ensure file is closed on all error paths
- Add TestFormatterFloatFormattingExtremeValuesUnlimitedWidth to test fallback with SQLCMDMAXVARTYPEWIDTH=0
- Ensures extreme values use scientific notation even with unlimited width

Co-authored-by: dlevy-msft-sql <194277063+dlevy-msft-sql@users.noreply.github.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 10 changed files in this pull request and generated 1 comment.

@dlevy-msft-sql
Copy link
Contributor

@copilot fix the review comment

Co-authored-by: dlevy-msft-sql <194277063+dlevy-msft-sql@users.noreply.github.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 10 changed files in this pull request and generated no new comments.

@dlevy-msft-sql dlevy-msft-sql marked this pull request as ready for review February 2, 2026 03:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working in-review Size: S Small issue (less than one week effort)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inconsistent Float Formatting Between go-sqlcmd and ODBC SQLCMD Versions

2 participants