Latest Results
feat(fuzz): row-encoding fuzz target for vortex-row (#8955)
Follow-up to #8937, where @a10y suggested a fuzz target for vortex-row.
## What it does
Adds a `row_encode` fuzz target that generates 1โ3 same-length columns
of arbitrary dtypes (via the existing `ArbitraryArray` machinery) with
independent per-column `RowSortField` configs (descending ร
nulls-first), and checks three properties:
1. **Sizes**: `compute_row_sizes`' per-row `{fixed, var}` totals equal
the encoded key lengths.
2. **Order**: byte comparison of any two keys equals a logical tuple
comparison of the row values, asserted pairwise. The oracle is
independent of the encoder: it lifts each row into a `RowKey` (recursive
over structs/FSLs) and compares with the byte format's exact semantics,
nulls positioned by `nulls_first` regardless of direction, and
`descending` inverting leaf values only.
3. **Cross-chunk**: keys from *separate* encode calls over different
slices of the same columns, still compare correctly against each other.
This is the contract sort/top-k operators rely on when comparing keys
across batches.
Unsupported dtypes (List, Variant, Union, Extension, Decimal256) assert
that the encoder rejects them. The target is wired into the scheduled
fuzz workflow like the existing targets.
---------
Signed-off-by: Nemo Yu <zyu379@wisc.edu> fix(vortex-row): decimal sort keys are not memcmp-comparable across chunks (#8937)
## The bug
`ORDER BY` / top-k on a decimal column silently returns wrongly ordered
rows whenever the column spans chunks whose physical value widths
differ. No error is raised; the output looks plausible.
### Example
Take a `DECIMAL(7, 5)` tip column and `ORDER BY tip DESC`. Two rows land
in different chunks, and compression picks a different physical width
for each chunk:
| row | unscaled value | chunk's physical type |
|---|---|---|
| `tip = 0.00484` | `484` | **i16** (484 doesn't fit i8) |
| `tip = 0.00091` | `91` | **i8** (every value in its chunk fits i8) |
A descending sort key is built per chunk: write the value big-endian,
flip the sign bit, invert the value bytes (for DESC), and prefix the
non-null sentinel `0xFE`. Before this fix, the number of value bytes
came from the *chunk's* type:
```
tip = 0.00484, encoded at its chunk's width (i16 โ 2 value bytes)
big-endian bytes 01 E4
flip sign bit 81 E4
invert for DESC 7E 1B
prepend sentinel FE 7E 1B โ 3-byte key
tip = 0.00091, encoded at its chunk's width (i8 โ 1 value byte)
big-endian bytes 5B
flip sign bit DB
invert for DESC 24
prepend sentinel FE 24 โ 2-byte key
```
The sorter compares keys with `memcmp`:
```
key(0.00484) = FE 7E 1B
key(0.00091) = FE 24
โโฌ โโฌ
โ โโ byte 1 decides: 0x24 < 0x7E, so key(0.00091) is the smaller key
โโ byte 0: equal
```
But byte 1 means different things in the two keys: in the 3-byte key it
is the *high-order* byte of a two-byte number, while in the 2-byte key
it is the *only* byte of a one-byte number. The comparison is
meaningless โ and DESC encoding promises **bigger value โ smaller key**,
so the smaller key wins: the sorter ranks `0.00091` as a larger tip than
`0.00484`. Ascending breaks symmetrically.
With this fix, both chunks encode at the width the *declared* dtype
implies (`DECIMAL(7,5)` โ i32 โ 4 value bytes), whatever their physical
storage:
```
key(0.00484) = FE 7F FF FE 1B
key(0.00091) = FE 7F FF FF A4
โโฌ
โโ byte 3 decides: 0xFE < 0xFF, so key(0.00484) is smaller
```
Equal-length keys, every byte position aligned โ `0.00484` correctly
sorts first in the descending order.
## The fix
Derive the key width from the declared dtype, so every chunk of a column
encodes identical-length keys:
- `decimal_key_type` (vortex-row): the dtype-derived width, used by both
the sizing pass and the encode dispatch.
- `converted_buffer<W>` (vortex-array, next to `widened_buffer`):
returns a chunk's values at exactly width `W`. Zero-copy when the chunk
is already stored at `W` (the common case), lossless widening otherwise,
and an error for any value that violates its declared precision, such
values previously encoded a garbage key silently.
The encode loop itself is unchanged; only where it reads its values from
changed.
---------
Signed-off-by: Nemo Yu <zyu379@wisc.edu>
Co-authored-by: Robert Kruszewski <github@robertk.io> Latest Branches
0%
nemo/fold-cast-of-literal 0%
0%
ยฉ 2026 CodSpeed Technology