Skip to content

fix(sql): reject invalid LIKE/ILIKE escape strings#7810

Open
wjones127 wants to merge 1 commit into
lance-format:mainfrom
wjones127:fix/like-escape-validation
Open

fix(sql): reject invalid LIKE/ILIKE escape strings#7810
wjones127 wants to merge 1 commit into
lance-format:mainfrom
wjones127:fix/like-escape-validation

Conversation

@wjones127

@wjones127 wjones127 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Previously the escape character in LIKE/ILIKE ... ESCAPE '<char>' was extracted with char.chars().next(), which silently accepted invalid input:

  • ... LIKE 'x' ESCAPE '' → treated as no escape.
  • ... LIKE 'x' ESCAPE 'ab' → silently truncated to 'a'.

Both changed predicate behavior instead of surfacing an error, contradicting the "reject invalid values at API boundaries" guideline.

This PR extracts the (previously duplicated) escape parsing into parse_like_escape_char, which requires exactly one character and returns a descriptive input error for empty or multi-character escapes. Added a test covering the valid single-char case plus empty/multi-char rejection, for both LIKE and ILIKE.

Fixes #7804

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved validation for LIKE and ILIKE escape characters.
    • Empty, multi-character, or improperly formatted escape values now return clear errors instead of being silently accepted or truncated.
    • Valid single-character escape values continue to work as expected.

Previously the escape character in `LIKE/ILIKE ... ESCAPE '<char>'` was
extracted with `char.chars().next()`, which silently accepted invalid
input: an empty escape (`ESCAPE ''`) became "no escape", and a
multi-character escape (`ESCAPE 'ab'`) was truncated to its first
character. Both changed predicate behavior instead of surfacing an error.

Extract the shared parsing into `parse_like_escape_char`, which requires
exactly one character and returns a descriptive input error otherwise.

Fixes lance-format#7804
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The planner adds strict validation for LIKE and ILIKE escape strings, rejects empty or multi-character values, and adds tests for valid and invalid escape inputs.

Changes

LIKE escape validation

Layer / File(s) Summary
Escape parsing and operator integration
rust/lance-datafusion/src/planner.rs
Adds shared validation requiring exactly one quoted escape character, applies it to LIKE and ILIKE, and tests valid, empty, and multi-character escape strings.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: westonpace, xuanwo

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states the main change: stricter validation of LIKE/ILIKE escape strings.
Linked Issues check ✅ Passed The PR requires exactly one escape character, rejects empty and multi-character values, and adds the requested tests for LIKE and ILIKE.
Out of Scope Changes check ✅ Passed The changes stay focused on escape parsing and related tests, with no unrelated code paths introduced.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bug Something isn't working label Jul 15, 2026
@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.57143% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance-datafusion/src/planner.rs 88.57% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

@wjones127
wjones127 marked this pull request as ready for review July 15, 2026 21:05

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/lance-datafusion/src/planner.rs`:
- Around line 1472-1475: Update the filter cases in the parse_filter test to
include each expression’s expected operator mode, then assert the corresponding
Like case-insensitive flag along with escape_char. Ensure LIKE expects
case-sensitive mode and ILIKE expects case-insensitive mode, while preserving
the existing error handling.
- Around line 1472-1486: Convert the parameterized LIKE/ILIKE test loops in the
planner test to rstest cases: add the appropriate #[rstest] test structure and
represent each filter input with #[case] parameters. Preserve the existing
assertions and rejection behavior for custom, empty, and multi-character escape
values.
- Around line 1487-1492: Update the test around planner.parse_filter to assert
that the returned error matches the concrete invalid-input error variant, while
retaining the existing “Invalid escape character in LIKE expression” message
check. Ensure the assertion rejects other error categories with similar text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d2f044be-6198-4d39-b56d-d47148058fc6

📥 Commits

Reviewing files that changed from the base of the PR and between 9effdb1 and b4023b4.

📒 Files selected for processing (1)
  • rust/lance-datafusion/src/planner.rs

Comment on lines +1472 to +1475
for filter in ["s LIKE 'a!%' ESCAPE '!'", "s ILIKE 'a!%' ESCAPE '!'"] {
match planner.parse_filter(filter).unwrap() {
Expr::Like(like) => assert_eq!(like.escape_char, Some('!'), "{filter}"),
other => panic!("expected a LIKE expression for `{filter}`, got {other:?}"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the operator mode too.

The valid cases only check escape_char, so a regression in the LIKE/ILIKE case-insensitive flag would pass. Include the expected operator mode in each case and assert the corresponding Like flag.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance-datafusion/src/planner.rs` around lines 1472 - 1475, Update the
filter cases in the parse_filter test to include each expression’s expected
operator mode, then assert the corresponding Like case-insensitive flag along
with escape_char. Ensure LIKE expects case-sensitive mode and ILIKE expects
case-insensitive mode, while preserving the existing error handling.

Comment on lines +1472 to +1486
for filter in ["s LIKE 'a!%' ESCAPE '!'", "s ILIKE 'a!%' ESCAPE '!'"] {
match planner.parse_filter(filter).unwrap() {
Expr::Like(like) => assert_eq!(like.escape_char, Some('!'), "{filter}"),
other => panic!("expected a LIKE expression for `{filter}`, got {other:?}"),
}
}

// Empty and multi-character escapes are rejected rather than silently
// dropped or truncated to the first character.
for filter in [
"s LIKE 'x' ESCAPE ''",
"s LIKE 'x' ESCAPE 'ab'",
"s ILIKE 'x' ESCAPE ''",
"s ILIKE 'x' ESCAPE 'ab'",
] {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use rstest for these parameterized cases.

These loops differ only by input values and should be expressed with #[rstest] and #[case], as required for Rust parameterized tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance-datafusion/src/planner.rs` around lines 1472 - 1486, Convert the
parameterized LIKE/ILIKE test loops in the planner test to rstest cases: add the
appropriate #[rstest] test structure and represent each filter input with
#[case] parameters. Preserve the existing assertions and rejection behavior for
custom, empty, and multi-character escape values.

Source: Coding guidelines

Comment on lines +1487 to +1492
let err = planner.parse_filter(filter).unwrap_err();
assert!(
err.to_string()
.contains("Invalid escape character in LIKE expression"),
"unexpected error for `{filter}`: {err}"
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert the invalid-input error variant as well as its message.

The test currently verifies only a message substring. Match the concrete invalid-input variant too, so a different error category with similar text cannot pass.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance-datafusion/src/planner.rs` around lines 1487 - 1492, Update the
test around planner.parse_filter to assert that the returned error matches the
concrete invalid-input error variant, while retaining the existing “Invalid
escape character in LIKE expression” message check. Ensure the assertion rejects
other error categories with similar text.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid LIKE/ILIKE escape strings are silently accepted instead of rejected

1 participant