You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An oversized decimal/numeric value converted through SQLGetData into a numeric C type reports SQLSTATE 07006 ("restricted data type attribute violation") instead of 22003 ("numeric value out of range").
07006 means the requested C type is not a legal target for this SQL type. But decimal → SQL_C_SLONGis a legal pairing — it is this particular value that will not fit. An application branching on SQLSTATE to distinguish "I asked for the wrong type" from "this row had a value too big for the buffer I gave you" gets the wrong answer, and the first is not retryable per-row while the second is.
The root cause is that numeric_source in mssql-odbc/src/api/fetch_convert.rs returns Option, so two distinct conditions collapse into the same None:
ColumnValues::Decimal(d) | ColumnValues::Numeric(d) => {let m = i128::try_from(d.magnitude()?).ok()?;// <- both failures return None
...}
None legitimately means 07006 for a binary or GUID column, which has no numeric interpretation at all. For a decimal column it means the value overflowed. Swapping the error at the Decimal arm alone is not enough — numeric_source needs to distinguish the two, e.g. by returning Result<NumericSource, ConvError> or by having the decimal arm signal out-of-range separately.
The same function already returns 22003 for the analogous character-source case (fetch_convert.rs:250), so fixing this also makes the function self-consistent.
This is reachable from the wire, not only through FFI. Two failure modes reach the ?:
i128::try_from fails for a four-word magnitude above i128::MAX. The wire decoder validates the word count, not that the magnitude fits the column's declared precision, so a 17-byte payload — a legal length — carrying a magnitude in (i128::MAX, u128::MAX] is accepted by the decoder and fails here. A valid decimal(38, s) never exceeds 10^38 - 1 ≈ 2^126.2, so this is a malformed or hostile payload, but it is one a server can send.
Case 2 also means the string and typed paths disagree about the same value: SQL_C_CHAR renders it, SQL_C_SLONG reports the conversion as illegal.
Steps to reproduce
Verified against mssql-odbc at 39532382 with a temporary unit test (since case 2 needs a malformed payload a real server will not normally send):
use mssql_tds::datatypes::decoder::DecimalParts;// A 17-byte wire payload of all 0xFF: 4 words, legal length, magnitude u128::MAX.let d = DecimalParts{is_positive:true,scale:0,precision:38,int_parts:vec![-1, -1, -1, -1],};letmut out:i64 = 0;letmut ind:SqlLen = 0;let err = unsafe{convert_integer_c(&ColumnValues::Decimal(d),SQL_C_SBIGINT,(&mut out as*muti64).cast(),&mut ind,)};
Equivalently over a live connection: have a server send a decimal(38, 0) column whose 16-byte magnitude exceeds i128::MAX, then SQLGetData that column into SQL_C_SBIGINT.
Expected behavior
SQLGetData returns SQL_ERROR with SQLSTATE 22003, "Numeric value out of range" — the conversion is legal, the value does not fit.
07006 should remain reserved for source/target pairings that are genuinely illegal, such as a binary or GUID column into a numeric C type.
Actual behavior
SQLGetData returns SQL_ERROR with SQLSTATE 07006, "Restricted data type attribute violation", implying decimal cannot be fetched as an integer at all.
Internally: ConvError::Restricted rather than ConvError::OutOfRange.
Version
mssql-odbc 0.1.0, commit 39532382 (branch for #237)
Affected crate
mssql-odbc
Note: mssql-odbc is not in the "Affected crate" dropdown in .github/ISSUE_TEMPLATE/bug_report.yml, which still lists only mssql-tds, mssql-js, mssql-tds-cli, mssql-mock-tds, and mssql-py-core. Worth adding separately.
Additional context
Came out of review discussion on #237 (comment). The 07006 behavior arrived with #217; #237 made numeric_source the single shared entry point for both the string and typed paths, so the fix is now in one place rather than two.
One unit test pins the current behavior: decimal_limbs_are_reassembled_and_bounded in fetch_convert.rs asserts ConvError::Restricted for vec![1, 0, 0, 0, 1]. It will need updating, and it is the right place to add a case for the wire-reachable i128::MAX boundary above, which nothing currently covers.
I grepped for 07006 across mssql-odbc: no ODBC e2e test pins it for the decimal path, so the blast radius is that one unit test. bind_param.rs's unsupported_conversion_returns_07006 is a different code path and unaffected.
Worth checking msodbcsql parity while in here — the pipeline already runs a parity comparison against the reference driver, so it can confirm which SQLSTATE msodbcsql returns for the same input rather than reasoning from the spec alone.
Describe the bug
An oversized
decimal/numericvalue converted throughSQLGetDatainto a numeric C type reports SQLSTATE07006("restricted data type attribute violation") instead of22003("numeric value out of range").07006means the requested C type is not a legal target for this SQL type. Butdecimal→SQL_C_SLONGis a legal pairing — it is this particular value that will not fit. An application branching on SQLSTATE to distinguish "I asked for the wrong type" from "this row had a value too big for the buffer I gave you" gets the wrong answer, and the first is not retryable per-row while the second is.The root cause is that
numeric_sourceinmssql-odbc/src/api/fetch_convert.rsreturnsOption, so two distinct conditions collapse into the sameNone:and the sole caller maps every
Noneto07006:Nonelegitimately means07006for a binary or GUID column, which has no numeric interpretation at all. For adecimalcolumn it means the value overflowed. Swapping the error at theDecimalarm alone is not enough —numeric_sourceneeds to distinguish the two, e.g. by returningResult<NumericSource, ConvError>or by having the decimal arm signal out-of-range separately.The same function already returns
22003for the analogous character-source case (fetch_convert.rs:250), so fixing this also makes the function self-consistent.This is reachable from the wire, not only through FFI. Two failure modes reach the
?:d.magnitude()returnsNonefor more than four significant words. After Guard decimal magnitude reassembly against 128-bit shift overflow #237 the wire decoder caps the count at 4, so this one is FFI-only.i128::try_fromfails for a four-word magnitude abovei128::MAX. The wire decoder validates the word count, not that the magnitude fits the column's declared precision, so a 17-byte payload — a legal length — carrying a magnitude in(i128::MAX, u128::MAX]is accepted by the decoder and fails here. A validdecimal(38, s)never exceeds10^38 - 1 ≈ 2^126.2, so this is a malformed or hostile payload, but it is one a server can send.Case 2 also means the string and typed paths disagree about the same value:
SQL_C_CHARrenders it,SQL_C_SLONGreports the conversion as illegal.Steps to reproduce
Verified against
mssql-odbcat39532382with a temporary unit test (since case 2 needs a malformed payload a real server will not normally send):Observed output:
Equivalently over a live connection: have a server send a
decimal(38, 0)column whose 16-byte magnitude exceedsi128::MAX, thenSQLGetDatathat column intoSQL_C_SBIGINT.Expected behavior
SQLGetDatareturnsSQL_ERRORwith SQLSTATE22003, "Numeric value out of range" — the conversion is legal, the value does not fit.07006should remain reserved for source/target pairings that are genuinely illegal, such as a binary or GUID column into a numeric C type.Actual behavior
SQLGetDatareturnsSQL_ERRORwith SQLSTATE07006, "Restricted data type attribute violation", implyingdecimalcannot be fetched as an integer at all.Internally:
ConvError::Restrictedrather thanConvError::OutOfRange.Version
mssql-odbc0.1.0, commit39532382(branch for #237)Affected crate
mssql-odbcNote:
mssql-odbcis not in the "Affected crate" dropdown in.github/ISSUE_TEMPLATE/bug_report.yml, which still lists onlymssql-tds,mssql-js,mssql-tds-cli,mssql-mock-tds, andmssql-py-core. Worth adding separately.Additional context
Came out of review discussion on #237 (comment). The 07006 behavior arrived with #217; #237 made
numeric_sourcethe single shared entry point for both the string and typed paths, so the fix is now in one place rather than two.Notes for whoever picks this up:
decimal_limbs_are_reassembled_and_boundedinfetch_convert.rsassertsConvError::Restrictedforvec![1, 0, 0, 0, 1]. It will need updating, and it is the right place to add a case for the wire-reachablei128::MAXboundary above, which nothing currently covers.07006acrossmssql-odbc: no ODBC e2e test pins it for the decimal path, so the blast radius is that one unit test.bind_param.rs'sunsupported_conversion_returns_07006is a different code path and unaffected.mssql-tdsissue, and is related in spirit to mssql-tds: SQL_VARIANT narrows a u32 data length to u8, desynchronizing the token stream #280.