Summary
IN LIST evaluates expressions such as:
When the right-hand list is constant, DataFusion can build a membership filter once and reuse it for every input row. Dynamic-filter pushdown can apply the same check millions of times during a scan.
This epic adds exact lookup strategies selected by physical representation and non-null list length. If none applies, DataFusion uses the generic static filter.
All strategies share the same result construction, so this work does not change SQL behavior for IN, NOT IN, input nulls, nulls in the list, dictionaries, or sliced arrays.
Stack
The PRs should be read in this order.
Landed
Remaining
How the strategies work
Generic fallback
#21927 improves the path available to every supported type. It precomputes Arrow hashes for the constant list, stores list indexes in a compact hash table, and uses Arrow's exact comparator to confirm equality. Membership is produced as a bitmap and then combined with input validity, list nulls, and NOT IN semantics by shared result-building code.
Bitmap lookup for small fixed domains
A one- or two-byte value has only 256 or 65,536 possible bit patterns. That entire domain can be represented by one bit per pattern:
UInt8 and Int8 use a 256-bit (32-byte) bitmap.
UInt16, Int16, and Float16 use a 65,536-bit (8 KiB) bitmap.
Building the filter sets the bit corresponding to each non-null list value. Probing a row is then one indexed bit test, with no hashing and no scan of the list. Signed integers and Float16 map their native bit patterns directly into the same finite bitmap domain.
Direct comparisons for very small primitive lists
For a tiny fixed-width list, a hash lookup can cost more than comparing the input directly with every constant. #23014 stores the constants in a fixed-size array and combines all equality checks into a predictable comparison chain.
The maximum non-null list sizes are:
| Physical width |
Direct comparisons through |
| 1 byte |
16 values |
| 2 bytes |
8 values |
| 4 bytes |
32 values |
| 8 bytes |
16 values |
| 16 bytes |
4 values |
Larger one- and two-byte lists use the bitmap strategy. Larger native 32- and 64-bit integer lists use primitive hash sets; #24102 extends that same shared fallback to Decimal128. Other unsupported primitive types continue through their existing exact filter or the generic fallback.
Shared primitive selection and Decimal128
#24102 puts the direct-comparison thresholds and larger-list choices in one primitive selector. Native arrays and representation adapters call that selector, so there is no second dispatch table.
For native Decimal128, up to four non-null list values use direct comparisons. Larger lists store and probe the native unscaled i128 values in the shared primitive hash-set filter instead of using the generic Arrow array filter. Only membership storage changes; decimal expression typing, precision/scale compatibility, null behavior, and exact native-value equality remain unchanged.
FixedSizeBinary
#24102 recognizes FixedSizeBinary widths that exactly match an existing primitive representation. The list and input are converted in the same way, so primitive equality and hashing are exact equality over the original fixed-width bytes; no numeric or decimal operations are involved.
FixedSizeBinary width |
Internal key |
Small lists |
Larger lists |
| 1 byte |
UInt8 |
direct comparisons through 16 values |
bitmap |
| 2 bytes |
UInt16 |
direct comparisons through 8 values |
bitmap |
| 4 bytes |
UInt32 |
direct comparisons through 32 values |
shared primitive hash-set filter |
| 8 bytes |
UInt64 |
direct comparisons through 16 values |
shared primitive hash-set filter |
| 16 bytes |
128-bit primitive |
direct comparisons through 4 values |
shared primitive hash-set filter |
Other widths keep using the generic filter.
Aligned Arrow buffers are reused directly. If a valid array has an unaligned buffer, the adapter copies its values into aligned primitive storage first. Alignment affects whether a copy is needed, not correctness.
Inline Utf8View and BinaryView
Arrow stores a Utf8View or BinaryView value of at most 12 bytes completely inside its 128-bit view: the view contains the length and the zero-padded bytes. For these values, the view holds the full value, so view equality is exact and needs no backing-buffer read.
#24088 uses this representation only when every non-null value in the constant list is inline:
- up to four non-null values use the existing 128-bit direct-comparison filter;
- larger lists reuse the shared primitive hash-set filter over the same 128-bit key; and
- if any list value is longer than 12 bytes, the whole list uses the generic filter.
Input arrays may still contain long values. Their encoded length distinguishes them from every inline list key, so they produce a miss without reading their backing bytes. Utf8View and BinaryView remain separate typed paths, and dictionary inputs continue through the shared dictionary handling.
Expected impact
| Case |
Lookup used |
| Generic constant lists |
precomputed Arrow hash table with exact comparison |
| One- and two-byte primitive domains |
one bitmap bit test per row |
| Very small primitive lists |
fixed direct-comparison chain |
Larger native Decimal128 lists |
shared primitive hash-set lookup |
Supported FixedSizeBinary widths |
reused primitive direct, bitmap, or hash-set lookup |
All-inline Utf8View / BinaryView lists |
exact 128-bit direct or hash-set lookup |
Summary
IN LISTevaluates expressions such as:When the right-hand list is constant, DataFusion can build a membership filter once and reuse it for every input row. Dynamic-filter pushdown can apply the same check millions of times during a scan.
This epic adds exact lookup strategies selected by physical representation and non-null list length. If none applies, DataFusion uses the generic static filter.
All strategies share the same result construction, so this work does not change SQL behavior for
IN,NOT IN, input nulls, nulls in the list, dictionaries, or sliced arrays.Stack
The PRs should be read in this order.
Landed
UInt8UInt16Int8andInt16Float16Remaining
Decimal128and supportedFixedSizeBinarywidthsUtf8ViewandBinaryViewlists (stacked on IN LIST: reuse primitive filters for FixedSizeBinary and Decimal128 #24102)How the strategies work
Generic fallback
#21927 improves the path available to every supported type. It precomputes Arrow hashes for the constant list, stores list indexes in a compact hash table, and uses Arrow's exact comparator to confirm equality. Membership is produced as a bitmap and then combined with input validity, list nulls, and
NOT INsemantics by shared result-building code.Bitmap lookup for small fixed domains
A one- or two-byte value has only 256 or 65,536 possible bit patterns. That entire domain can be represented by one bit per pattern:
UInt8andInt8use a 256-bit (32-byte) bitmap.UInt16,Int16, andFloat16use a 65,536-bit (8 KiB) bitmap.Building the filter sets the bit corresponding to each non-null list value. Probing a row is then one indexed bit test, with no hashing and no scan of the list. Signed integers and
Float16map their native bit patterns directly into the same finite bitmap domain.Direct comparisons for very small primitive lists
For a tiny fixed-width list, a hash lookup can cost more than comparing the input directly with every constant. #23014 stores the constants in a fixed-size array and combines all equality checks into a predictable comparison chain.
The maximum non-null list sizes are:
Larger one- and two-byte lists use the bitmap strategy. Larger native 32- and 64-bit integer lists use primitive hash sets; #24102 extends that same shared fallback to
Decimal128. Other unsupported primitive types continue through their existing exact filter or the generic fallback.Shared primitive selection and
Decimal128#24102 puts the direct-comparison thresholds and larger-list choices in one primitive selector. Native arrays and representation adapters call that selector, so there is no second dispatch table.
For native
Decimal128, up to four non-null list values use direct comparisons. Larger lists store and probe the native unscaledi128values in the shared primitive hash-set filter instead of using the generic Arrow array filter. Only membership storage changes; decimal expression typing, precision/scale compatibility, null behavior, and exact native-value equality remain unchanged.FixedSizeBinary#24102 recognizes
FixedSizeBinarywidths that exactly match an existing primitive representation. The list and input are converted in the same way, so primitive equality and hashing are exact equality over the original fixed-width bytes; no numeric or decimal operations are involved.FixedSizeBinarywidthUInt8UInt16UInt32UInt64Other widths keep using the generic filter.
Aligned Arrow buffers are reused directly. If a valid array has an unaligned buffer, the adapter copies its values into aligned primitive storage first. Alignment affects whether a copy is needed, not correctness.
Inline
Utf8ViewandBinaryViewArrow stores a
Utf8VieworBinaryViewvalue of at most 12 bytes completely inside its 128-bit view: the view contains the length and the zero-padded bytes. For these values, the view holds the full value, so view equality is exact and needs no backing-buffer read.#24088 uses this representation only when every non-null value in the constant list is inline:
Input arrays may still contain long values. Their encoded length distinguishes them from every inline list key, so they produce a miss without reading their backing bytes.
Utf8ViewandBinaryViewremain separate typed paths, and dictionary inputs continue through the shared dictionary handling.Expected impact
Decimal128listsFixedSizeBinarywidthsUtf8View/BinaryViewlists