Repository navigation
Conversation
MONEY and SMALLMONEY travel as a scaled integer (value * 10^4) in an i64 or i32. The decoder divides it in f64, which holds integers exactly only up to 2^53, so large values lose digits: 900719925474099.0001 reads back as 900719925474099.0, and both MONEY extremes round. The new default-off `exact-money` feature decodes both types as a scale-4 Numeric, combining the words as (high << 32) | low in an i64. The feature switches the decoded value and every money null placeholder together, so a build never mixes F64 values with Numeric(None) nulls. With `exact-money`, a nullable SMALLMONEY (VarLenSized(Money) with length 4) reports ColumnType::Money4, matching its NOT NULL form, the way Intn and Floatn already map their widths. Length 8 stays Money. With `exact-money`, row.get::<f64, _>() on a money column panics and row.try_get::<f64, _>() returns Error::Conversion, populated or NULL. Read these columns as Numeric, or as Decimal or BigDecimal with the `rust_decimal` or `bigdecimal` features. Encoding is unchanged. VarLenSizedPrecision(Money) stays a protocol error. Default builds are unchanged: values are F64, a MONEYN null and its placeholder are F64(None), the fixed-length SMALLMONEY placeholder is F32(None), and nullable SMALLMONEY reports ColumnType::Money. Unit and live tests pin the decoder, the placeholders, the column types and the get/try_get contract in both feature states.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MONEY and SMALLMONEY travel on the wire as a scaled integer: value × 10,000. MONEY uses an i64, SMALLMONEY an i32. Tiberius decodes them with
((high << 32) as f64 + low) / 1e4. An f64 holds integers exactly only up to 2^53. MONEY's scaled range reaches about 9.2 × 10^18.So digits disappear before the value reaches the caller:
Both columns read back as
900719925474099.0. Both MONEY extremes round too: 922337203685477.5807 reads as 922337203685477.6.Change
This PR adds a default-off Cargo feature,
exact-money. It adds no dependency and no new value type.With the feature on:
ColumnData::Numeric(Some(Numeric::new_with_scale(scaled, 4))).(high << 32) | lowin an i64, so every value is exact.ColumnData::Numeric(None). That covers the MONEYN length-0 value and every money metadata null placeholder.With the feature off, decoding is unchanged from current
main:ColumnData::F64, through the same arithmetic.F64(None).F32(None).The feature gates the decoder and the placeholders together. A build never mixes
F64values withNumeric(None)nulls.The feature also keeps the width of nullable money metadata. A nullable SMALLMONEY arrives as
VarLenSized(Money, 4). With the feature on,Column::column_type()reports it asColumnType::Money4, like its NOT NULLFixedLen(Money4)form. Length 8 staysColumnType::Money. This follows howIntnandFloatnalready map their widths. Callers that derive a decimal precision from the column type can then give SMALLMONEY precision 10 whatever its nullability. Without the feature, nullable SMALLMONEY still reportsColumnType::Money, as it does today.TypeInfo::VarLenSizedPrecision { ty: Money, .. }still fails withError::Protocol. MONEY type info carries no precision or scale, so that form is never valid. The existing testdecode_varlen_sized_precision_unsupported_errorskeeps pinning it.Contract with
exact-moneyonrow.get::<f64, _>()on a money or smallmoney column panics, populated or NULL.row.try_get::<f64, _>()returnsError::Conversion.Numeric.rust_decimal::Decimalwith therust_decimalfeature.BigDecimalwith thebigdecimalfeature.f64orNumericfor money columns.ColumnType::Money4.Tests
Run both feature states:
cargo test, thencargo test --features exact-money.ColumnData::decode:FixedLen(Money/Money4)andVarLenSized(Money, 8/4).row::testspins the accessor contract per state, through the real decoder and metadata placeholders.row::testspinsColumnTypeforVarLenSized(Money)at lengths 8 and 4 in each state.money_smallmoney_column_typespins the column type of all 4 money columns per state, on a populated and a zero-row result.money_accessor_contractpinsgetandtry_geton NOT NULL and nullable columns, populated and NULL.money_smallmoney_decode_exactlyround-trips the large values and extremes through SQL Server.Why opt-in, and why
f64does not acceptNumericOpt-in keeps this a non-breaking change. Default builds see no change.
An alternative is to make
f64: FromSqlacceptNumericwith a lossy conversion. That would not recreate the decoder loss: the row would still hold the exactNumeric. ButFromSqlsees only theColumnData, not the column type. Sof64would also start accepting everynumericanddecimalcolumn. That broadens decimal conversion for all callers, so I left it out.A later breaking release could make
exact-moneythe default. That is the maintainers' call.