Skip to content

Commit d2b2378

Browse files
authored
Implement LIKE in vortex instead of falling back to arrow (#8709)
One more operation where we fallback to arrow instead of using our own implementation. We can leverage view array layouts to speed up certain comparisons
1 parent 2a58c67 commit d2b2378

6 files changed

Lines changed: 873 additions & 21 deletions

File tree

Cargo.lock

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

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ libfuzzer-sys = "0.4"
181181
libloading = "0.8"
182182
liblzma = "0.4"
183183
log = { version = "0.4.21" }
184+
memchr = "2.8.0"
184185
memmap2 = "0.9.5"
185186
mimalloc = "0.1.42"
186187
moka = { version = "0.12.10", default-features = false }
@@ -215,7 +216,9 @@ quote = "1.0.44"
215216
rand = "0.10.1"
216217
rand_distr = "0.6"
217218
ratatui = { version = "0.30", default-features = false }
218-
regex = "1.11.0"
219+
regex = "1.12"
220+
regex-automata = "0.4"
221+
regex-syntax = "0.8.9"
219222
reqwest = { version = "0.13.0", features = [
220223
"blocking",
221224
"charset",

vortex-array/Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ arrow-cast = { workspace = true }
2929
arrow-data = { workspace = true }
3030
arrow-schema = { workspace = true, features = ["canonical_extension_types"] }
3131
arrow-select = { workspace = true }
32-
arrow-string = { workspace = true }
3332
async-lock = { workspace = true }
3433
bytes = { workspace = true }
3534
cfg-if = { workspace = true }
@@ -43,6 +42,7 @@ humansize = { workspace = true }
4342
inventory = { workspace = true }
4443
itertools = { workspace = true }
4544
jiff = { workspace = true }
45+
memchr = { workspace = true }
4646
num-traits = { workspace = true }
4747
num_enum = { workspace = true }
4848
parking_lot = { workspace = true }
@@ -53,6 +53,8 @@ primitive-types = { workspace = true, optional = true, features = [
5353
] }
5454
prost = { workspace = true }
5555
rand = { workspace = true }
56+
regex = { workspace = true }
57+
regex-syntax = { workspace = true }
5658
rstest = { workspace = true, optional = true }
5759
rstest_reuse = { workspace = true, optional = true }
5860
rustc-hash = { workspace = true }
@@ -136,6 +138,10 @@ harness = false
136138
name = "kleene_bool"
137139
harness = false
138140

141+
[[bench]]
142+
name = "like"
143+
harness = false
144+
139145
[[bench]]
140146
name = "interleave"
141147
harness = false

vortex-array/benches/like.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
#![expect(clippy::unwrap_used)]
5+
6+
use divan::Bencher;
7+
use rand::RngExt;
8+
use rand::SeedableRng;
9+
use rand::distr::Uniform;
10+
use rand::prelude::StdRng;
11+
use vortex_array::ArrayRef;
12+
use vortex_array::IntoArray;
13+
use vortex_array::VortexSessionExecute;
14+
use vortex_array::arrays::BoolArray;
15+
use vortex_array::arrays::ConstantArray;
16+
use vortex_array::arrays::VarBinViewArray;
17+
use vortex_array::arrays::scalar_fn::ScalarFnFactoryExt;
18+
use vortex_array::scalar_fn::fns::like::Like;
19+
use vortex_array::scalar_fn::fns::like::LikeOptions;
20+
21+
fn main() {
22+
divan::main();
23+
}
24+
25+
const ARRAY_SIZE: usize = 2_048;
26+
27+
/// Random lowercase strings of 4..=24 bytes, some with a `hello` infix.
28+
fn strings() -> ArrayRef {
29+
let mut rng = StdRng::seed_from_u64(0);
30+
let len_dist = Uniform::new_inclusive(4usize, 24).unwrap();
31+
VarBinViewArray::from_iter_str((0..ARRAY_SIZE).map(|i| {
32+
let len = rng.sample(len_dist);
33+
let mut s: String = (0..len)
34+
.map(|_| char::from(rng.random_range(b'a'..=b'z')))
35+
.collect();
36+
if i % 7 == 0 {
37+
s.insert_str(len / 2, "hello");
38+
}
39+
s
40+
}))
41+
.into_array()
42+
}
43+
44+
fn bench_like(bencher: Bencher, pattern: &str, options: LikeOptions) {
45+
let session = vortex_array::array_session();
46+
let array = strings();
47+
bencher
48+
.with_inputs(|| {
49+
(
50+
Like.try_new_array(
51+
ARRAY_SIZE,
52+
options,
53+
[
54+
array.clone(),
55+
ConstantArray::new(pattern, ARRAY_SIZE).into_array(),
56+
],
57+
)
58+
.unwrap(),
59+
session.create_execution_ctx(),
60+
)
61+
})
62+
.bench_values(|(array, mut ctx)| array.execute::<BoolArray>(&mut ctx).unwrap());
63+
}
64+
65+
#[divan::bench]
66+
fn like_exact(bencher: Bencher) {
67+
bench_like(bencher, "hello", LikeOptions::default());
68+
}
69+
70+
#[divan::bench]
71+
fn like_prefix(bencher: Bencher) {
72+
bench_like(bencher, "hello%", LikeOptions::default());
73+
}
74+
75+
#[divan::bench]
76+
fn like_suffix(bencher: Bencher) {
77+
bench_like(bencher, "%hello", LikeOptions::default());
78+
}
79+
80+
#[divan::bench]
81+
fn like_contains(bencher: Bencher) {
82+
bench_like(bencher, "%hello%", LikeOptions::default());
83+
}
84+
85+
#[divan::bench]
86+
fn like_regex(bencher: Bencher) {
87+
bench_like(bencher, "h_llo%w%d", LikeOptions::default());
88+
}
89+
90+
#[divan::bench]
91+
fn like_per_row_patterns(bencher: Bencher) {
92+
let session = vortex_array::array_session();
93+
let array = strings();
94+
// A non-constant pattern child takes the per-row path; repeated patterns hit the
95+
// compile cache.
96+
let patterns = VarBinViewArray::from_iter_str((0..ARRAY_SIZE).map(|_| "hello%")).into_array();
97+
bencher
98+
.with_inputs(|| {
99+
(
100+
Like.try_new_array(
101+
ARRAY_SIZE,
102+
LikeOptions::default(),
103+
[array.clone(), patterns.clone()],
104+
)
105+
.unwrap(),
106+
session.create_execution_ctx(),
107+
)
108+
})
109+
.bench_values(|(array, mut ctx)| array.execute::<BoolArray>(&mut ctx).unwrap());
110+
}
111+
112+
#[divan::bench]
113+
fn ilike_contains(bencher: Bencher) {
114+
bench_like(
115+
bencher,
116+
"%HELLO%",
117+
LikeOptions {
118+
negated: false,
119+
case_insensitive: true,
120+
},
121+
);
122+
}

0 commit comments

Comments
 (0)