Skip to content

Commit a194856

Browse files
claudelalvarezt
authored andcommitted
perf(operations): add literal string fast paths for filter and replace
Add fast-path optimizations for filter, filter_not, and replace operations when patterns contain no regex metacharacters. This avoids unnecessary regex compilation and matching for simple literal string operations.
1 parent 9d720af commit a194856

File tree

1 file changed

+74
-18
lines changed

1 file changed

+74
-18
lines changed

src/pipeline/mod.rs

Lines changed: 74 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,21 +1354,69 @@ fn apply_single_operation(
13541354
apply_list_operation(val, |list| apply_range(&list, range), "Slice")
13551355
}
13561356
StringOp::Filter { pattern } => {
1357-
let re = get_cached_regex(pattern)?;
1357+
// Fast path for literal string matching (no regex metacharacters)
1358+
let is_literal = !pattern.contains([
1359+
'\\', '.', '*', '+', '?', '^', '$', '|', '[', ']', '(', ')', '{', '}',
1360+
]);
1361+
13581362
match val {
1359-
Value::List(list) => Ok(Value::List(
1360-
list.into_iter().filter(|s| re.is_match(s)).collect(),
1361-
)),
1362-
Value::Str(s) => Ok(Value::Str(if re.is_match(&s) { s } else { String::new() })),
1363+
Value::List(list) => {
1364+
if is_literal {
1365+
Ok(Value::List(
1366+
list.into_iter().filter(|s| s.contains(pattern)).collect(),
1367+
))
1368+
} else {
1369+
let re = get_cached_regex(pattern)?;
1370+
Ok(Value::List(
1371+
list.into_iter().filter(|s| re.is_match(s)).collect(),
1372+
))
1373+
}
1374+
}
1375+
Value::Str(s) => {
1376+
if is_literal {
1377+
Ok(Value::Str(if s.contains(pattern) {
1378+
s
1379+
} else {
1380+
String::new()
1381+
}))
1382+
} else {
1383+
let re = get_cached_regex(pattern)?;
1384+
Ok(Value::Str(if re.is_match(&s) { s } else { String::new() }))
1385+
}
1386+
}
13631387
}
13641388
}
13651389
StringOp::FilterNot { pattern } => {
1366-
let re = get_cached_regex(pattern)?;
1390+
// Fast path for literal string matching (no regex metacharacters)
1391+
let is_literal = !pattern.contains([
1392+
'\\', '.', '*', '+', '?', '^', '$', '|', '[', ']', '(', ')', '{', '}',
1393+
]);
1394+
13671395
match val {
1368-
Value::List(list) => Ok(Value::List(
1369-
list.into_iter().filter(|s| !re.is_match(s)).collect(),
1370-
)),
1371-
Value::Str(s) => Ok(Value::Str(if re.is_match(&s) { String::new() } else { s })),
1396+
Value::List(list) => {
1397+
if is_literal {
1398+
Ok(Value::List(
1399+
list.into_iter().filter(|s| !s.contains(pattern)).collect(),
1400+
))
1401+
} else {
1402+
let re = get_cached_regex(pattern)?;
1403+
Ok(Value::List(
1404+
list.into_iter().filter(|s| !re.is_match(s)).collect(),
1405+
))
1406+
}
1407+
}
1408+
Value::Str(s) => {
1409+
if is_literal {
1410+
Ok(Value::Str(if s.contains(pattern) {
1411+
String::new()
1412+
} else {
1413+
s
1414+
}))
1415+
} else {
1416+
let re = get_cached_regex(pattern)?;
1417+
Ok(Value::Str(if re.is_match(&s) { String::new() } else { s }))
1418+
}
1419+
}
13721420
}
13731421
}
13741422
StringOp::Sort { direction } => {
@@ -1429,16 +1477,24 @@ fn apply_single_operation(
14291477
flags,
14301478
} => {
14311479
if let Value::Str(s) = val {
1432-
// Early exit for simple string patterns (not regex)
1433-
if !flags.contains('g')
1434-
&& !pattern.contains([
1435-
'\\', '.', '*', '+', '?', '^', '$', '|', '[', ']', '(', ')', '{', '}',
1436-
])
1437-
&& !s.contains(pattern)
1438-
{
1439-
return Ok(Value::Str(s));
1480+
// Fast path for literal string replacement (no regex metacharacters or special flags)
1481+
let is_literal = !pattern.contains([
1482+
'\\', '.', '*', '+', '?', '^', '$', '|', '[', ']', '(', ')', '{', '}',
1483+
]);
1484+
1485+
// Only use fast path if no special regex flags (case-insensitive, multiline, etc.)
1486+
let has_special_flags = flags.chars().any(|c| c != 'g');
1487+
1488+
if is_literal && !has_special_flags {
1489+
let result = if flags.contains('g') {
1490+
s.replace(pattern, replacement)
1491+
} else {
1492+
s.replacen(pattern, replacement, 1)
1493+
};
1494+
return Ok(Value::Str(result));
14401495
}
14411496

1497+
// Regex path for complex patterns
14421498
let pattern_to_use = if flags.is_empty() {
14431499
pattern.clone()
14441500
} else {

0 commit comments

Comments
 (0)