Skip to content

Commit 37bd58d

Browse files
feat(vortex-onpair): port to onpair 0.1.0 API; add LIKE, list_contains, and take kernels
- Port the onpair encoding to the onpair 0.1.0 public API (raw column parts, validated dictionaries, MaxDictBits config). - Add compressed-domain LIKE pushdown backed by onpair prefix/substring search, plus list_contains and take compute kernels. - Depend on the published onpair 0.1.0 release from crates.io. Signed-off-by: Francesco Gargiulo <gargiulo.fr@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 204dacc commit 37bd58d

22 files changed

Lines changed: 1308 additions & 228 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ num_enum = { version = "0.7.3", default-features = false }
192192
object_store = { version = "0.13.2", default-features = false }
193193
once_cell = "1.21"
194194
oneshot = { version = "0.2.0", features = ["async"] }
195-
onpair = { version = "0.0.4" }
195+
onpair = "0.1.0"
196196
opentelemetry = "0.32.0"
197197
opentelemetry-otlp = "0.32.0"
198198
opentelemetry_sdk = "0.32.0"

encodings/experimental/onpair/README.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,27 @@ cascading-compressor support on every integer child.
1010

1111
## Compute
1212

13-
Like the FSST encoding, this crate provides `cast` and `filter`
14-
pushdown. Other operators fall back to ordinary decompression.
13+
Like the FSST encoding, this crate pushes down common operations over the
14+
encoded representation. It supports `cast`, `filter`, `take`, byte length,
15+
constant equality / inequality, simple constant `LIKE` patterns, and constant
16+
string-list membership (`IN (...)`). Unsupported operators or pattern shapes
17+
fall back to ordinary decompression.
1518

1619
## Default Configuration
1720

18-
The default training preset is **dict-12**: 12 bits per token,
19-
dictionary capped at 4 096 entries. Token codes are stored as a
20-
`PrimitiveArray<u16>`; downstream `FastLanes::BitPacking` losslessly
21-
narrows the child to exactly `bits`-bit codes on disk.
21+
The default training preset is **dict-12**: the OnPair trainer may build a
22+
dictionary with up to 4 096 tokens. After compression, the runtime code width is
23+
derived from the actual dictionary size. Vortex stores token codes as an integer
24+
child array; downstream integer compression may narrow or bit-pack that child
25+
independently of the OnPair metadata.
2226

2327
## Layout
2428

2529
- Buffer 0 — `dict_bytes`: dictionary blob built by the OnPair trainer,
2630
padded with `MAX_TOKEN_SIZE` trailing zero bytes so the over-copy
2731
decoder can read 16 bytes past the last token.
2832
- Slot 0 — `dict_offsets`: `PrimitiveArray<u32>`, len `dict_size + 1`.
29-
- Slot 1 — `codes`: `PrimitiveArray<u16>`, length `total_tokens`.
33+
- Slot 1 — `codes`: integer `PrimitiveArray`, length `total_tokens`.
3034
- Slot 2 — `codes_offsets`: `PrimitiveArray<u32>`, length `num_rows + 1`.
3135
- Slot 3 — `uncompressed_lengths`: integer `PrimitiveArray`, length
3236
`num_rows`.

encodings/experimental/onpair/benches/decode.rs

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//
44
//! Decode-path microbenchmarks for the OnPair Vortex array.
55
//!
6-
//! * `decompress_into` — the upstream `onpair::decompress_into` decoder hot
6+
//! * `decode_into` — the upstream `onpair::decode_into` decoder hot
77
//! loop, fed by pre-materialised [`DecodeInputs`]. Measures the inner loop
88
//! only (no child `execute`, no allocation).
99
//! * `canonicalize_to_varbinview` — the full Vortex
@@ -28,7 +28,7 @@ use std::mem::MaybeUninit;
2828
use std::sync::LazyLock;
2929

3030
use divan::Bencher;
31-
use onpair::Parts;
31+
use onpair::CompactDictionaryView;
3232
use vortex_array::ArrayRef;
3333
use vortex_array::ExecutionCtx;
3434
use vortex_array::IntoArray;
@@ -38,43 +38,45 @@ use vortex_array::arrays::PrimitiveArray;
3838
use vortex_array::arrays::VarBinArray;
3939
use vortex_array::arrays::VarBinViewArray;
4040
use vortex_array::arrays::filter::FilterKernel;
41+
use vortex_array::buffer::BufferHandle;
4142
use vortex_array::builtins::ArrayBuiltins;
4243
use vortex_array::dtype::DType;
4344
use vortex_array::dtype::NativePType;
4445
use vortex_array::dtype::Nullability;
4546
use vortex_buffer::Buffer;
46-
use vortex_buffer::ByteBuffer;
4747
use vortex_mask::Mask;
4848
use vortex_onpair::DEFAULT_DICT12_CONFIG;
4949
use vortex_onpair::OnPair;
5050
use vortex_onpair::OnPairArray;
5151
use vortex_onpair::OnPairArraySlotsExt;
5252

5353
/// Host-resident decode inputs, materialised once so the decode-loop benchmark
54-
/// measures only `onpair::decompress_into` (not child `execute`/allocation).
54+
/// measures only `onpair::decode_into` (not child `execute`/allocation).
5555
struct DecodeInputs {
56-
dict_bytes: ByteBuffer,
56+
dict_bytes: BufferHandle,
5757
dict_offsets: Buffer<u32>,
5858
codes: Buffer<u16>,
59-
bits: u32,
6059
}
6160

6261
impl DecodeInputs {
63-
fn as_parts(&self) -> Parts<'_> {
64-
Parts {
65-
dict_bytes: self.dict_bytes.as_slice(),
66-
dict_offsets: self.dict_offsets.as_slice(),
67-
bits: self.bits,
68-
codes: self.codes.as_slice(),
62+
fn dict(&self) -> CompactDictionaryView<'_> {
63+
// SAFETY: `materialise` validates this borrowed dictionary once after
64+
// widening offsets. The benchmark then keeps both buffers immutable.
65+
unsafe {
66+
CompactDictionaryView::new_unchecked(
67+
self.dict_bytes.as_host().as_slice(),
68+
self.dict_offsets.as_slice(),
69+
)
6970
}
7071
}
7172

72-
fn decompressed_len(&self) -> usize {
73-
onpair::decompressed_len(self.as_parts())
73+
fn decoded_len(&self) -> usize {
74+
onpair::decoded_len(self.codes.as_slice(), self.dict())
7475
}
7576

76-
fn decompress_into(&self, out: &mut [MaybeUninit<u8>]) -> usize {
77-
onpair::decompress_into(self.as_parts(), out)
77+
fn decode_into(&self, out: &mut [MaybeUninit<u8>]) -> usize {
78+
// SAFETY: callers allocate `decoded_len + DECODE_PADDING` bytes.
79+
unsafe { onpair::decode_into(self.codes.as_slice(), self.dict(), out) }
7880
}
7981
}
8082
use vortex_onpair::onpair_compress;
@@ -175,13 +177,16 @@ fn widen<T: NativePType>(arr: &ArrayRef, ctx: &mut ExecutionCtx) -> Buffer<T> {
175177

176178
fn materialise(arr: &OnPairArray, ctx: &mut ExecutionCtx) -> (DecodeInputs, usize) {
177179
let view = arr.as_view();
180+
let dict_offsets = widen::<u32>(view.dict_offsets(), ctx);
181+
let dict_bytes = view.dict_bytes_handle().clone();
182+
CompactDictionaryView::validate(dict_bytes.as_host().as_slice(), dict_offsets.as_slice())
183+
.expect("valid OnPair dictionary");
178184
let inputs = DecodeInputs {
179-
dict_bytes: view.dict_bytes().clone(),
180-
dict_offsets: widen::<u32>(view.dict_offsets(), ctx),
185+
dict_bytes,
186+
dict_offsets,
181187
codes: widen::<u16>(view.codes(), ctx),
182-
bits: view.bits(),
183188
};
184-
let total = inputs.decompressed_len();
189+
let total = inputs.decoded_len();
185190
(inputs, total)
186191
}
187192

@@ -194,16 +199,16 @@ const CASES: &[(Shape, usize)] = &[
194199
];
195200

196201
/// Raw decode loop time, excluding child `execute` and the output allocation.
197-
/// Hits `onpair::decompress_into` directly.
202+
/// Hits `onpair::decode_into` directly.
198203
#[divan::bench(args = CASES)]
199-
fn decompress_into_bench(bencher: Bencher, case: (Shape, usize)) {
204+
fn decode_into_bench(bencher: Bencher, case: (Shape, usize)) {
200205
let mut ctx = SESSION.create_execution_ctx();
201206
let (shape, n) = case;
202207
let arr = compress(n, shape, &mut ctx);
203208
let (inputs, total) = materialise(&arr, &mut ctx);
204209
bencher.bench_local(|| {
205-
let mut out: Vec<u8> = Vec::with_capacity(total);
206-
let written = inputs.decompress_into(out.spare_capacity_mut());
210+
let mut out: Vec<u8> = Vec::with_capacity(total + onpair::DECODE_PADDING);
211+
let written = inputs.decode_into(out.spare_capacity_mut());
207212
unsafe { out.set_len(written) };
208213
divan::black_box(out);
209214
});
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
 � ��(08
1+
� ��(08

0 commit comments

Comments
 (0)