Repository navigation
Add a canonical decimal width and import decimals at it - #10897
connortsui20 wants to merge 5 commits into
Conversation
Merging this PR will degrade performance by 14.94%3 warnings
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
67b1f4e to
f90f793
Compare
The canonical width of a decimal values buffer depends only on the precision. Add `DecimalDType::canonical_values_type()` as the one owner of that mapping, so that later changes can ask the dtype for its width instead of a free function on `DecimalType`. Test every precision boundary between two widths. Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
Migrate every call of `DecimalType::smallest_decimal_value_type` to `DecimalDType::canonical_values_type()` with the same dtype, and remove the old function. Arithmetic keeps its work dtype and casts keep their target dtype. `is_compatible_decimal_value_type` now compares against the canonical width. Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
Imports and decoders need one shared way to move decimal values to the canonical width of their dtype. copy_to_canonical_width copies values into DecimalDType::canonical_values_type(). A narrowing copy checks every valid value against the target precision, not only the width, and writes zeros into null slots. A widening or same-width copy checks nothing. The copy never rescales. check_precision fails on a valid value outside the precision and ignores null slots. It runs a branchless fold first and looks for the failing row only when the fold finds a value outside the precision. Both functions are public through vortex_array::arrays::decimal. The try_downcast macro drops its local BigCast import, because the module now imports it. Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
The Arrow decimal import kept the Arrow width, so a Decimal128 array of precision 5 became an i128 Vortex array, and no import checked that the values fit the precision. One private function now serves all four from_arrow_decimal* functions. When the Arrow width is the canonical width of the precision, it keeps the Arrow buffer and runs check_precision. Otherwise it copies with copy_to_canonical_width, which writes zeros into null slots when it narrows. The dtype now comes from DecimalDType::try_new, so an invalid precision or scale from FFI returns an error instead of a panic. The error names arrow-rs PrimitiveArray::null_if_overflow_precision, which turns values outside the precision into nulls. The expected arrays of the Decimal128 and Decimal256 conversion tests now use the canonical widths (i64 for precision 10, i128 for precision 38), and the tests assert the width. Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
The DuckDB decimal import narrowed i16 storage into i8 for precision 1 to 2 with a lossy `as` cast, so a value outside the precision, or garbage in a null slot, wrapped silently. It also checked no value against the precision. The i16, i32 and i64 widths now copy through copy_to_canonical_width, which checks a narrowing copy and writes zeros into the null slots. The same-width copies and the i128 buffer are checked with check_precision. The array is then built with the unchecked constructor. Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
f90f793 to
9836fef
Compare
Summary
Every decimal precision now has one canonical storage width,
DecimalDType::canonical_values_type(), and the Arrow and DuckDB imports produce arrays at that width. Before this, the Arrow import kept whatever width Arrow used (aDecimal128column at precision 10 arrived asi128), and the DuckDB import narrowed precision 1 and 2 with a lossyascast.This doesn't make the canonical width an invariant yet.
DecimalArraystill accepts any storage width, and every kernel, writer and exporter already handles mixed widths, so only the two imports change behavior. Enforcing the width intry_newcomes later in #10701, once DecimalByteParts decoding, the read path for old files and the compressor stop producing other widths.Changes
canonical_values_type()replacesDecimalType::smallest_decimal_value_typewith the same precision-to-width table, so the migrated call sites behave as before. Two shared helpers invortex_array::arrays::decimaldo the copies that the imports need now and that the later steps reuse.copy_to_canonical_widthcopies values into the canonical width, and a narrowing copy fails on a valid value outside the precision and writes zeros in null slots.check_precisionchecks values in place.The Arrow import stays zero-copy when the Arrow width already matches and copies otherwise. Either way, every valid value is checked against the precision, so a value outside it now fails at import instead of reaching
scalar_atormin_max, which can panic on it (#10749). The error points at arrow-rsPrimitiveArray::null_if_overflow_precisionfor callers that would rather get nulls. The dtype is built withDecimalDType::try_new, so an invalid precision or scale from FFI is an error rather than a panic. The DuckDB import goes through the same helpers.This overlaps with #10305, which touches many of the same files. For review, read the commits one at a time.
API Changes
DecimalType::smallest_decimal_value_typeis removed in favor ofDecimalDType::canonical_values_type(). Arrow and DuckDB decimal imports return arrays at the canonical width and fail on a valid value outside the declared precision.