Skip to content

Like predicate does not escape % and _ wildcards in the bound value #24

Description

@jkalias

Problem

Like::GetSqlValue (src/query_predicates.cc:97) builds the comparison value as %value% but does not escape % or _ characters contained in the value itself, and the generated LIKE clause (QueryPredicate::Evaluate) has no ESCAPE clause.

Impact

If the user-supplied value contains % or _, those characters act as LIKE wildcards rather than literals, so the match semantics differ from what the caller intended (e.g. searching for 50% matches far more than the literal text). The value is still bound as a parameter, so this is a correctness/robustness problem rather than injection.

Suggested direction

Escape %, _ (and the escape character) in the bound value and emit a corresponding ESCAPE '\' clause, so the user's text is matched literally.

Implementation note — the ESCAPE clause must survive Clone()

The value escaping and the ESCAPE clause are a pair: SQLite's LIKE has no default escape character, so escaping the bound value only works if the SQL also declares ESCAPE '\'. Naively overriding Like::Evaluate() to append the clause is not sufficient, because of how predicates are cloned:

  • QueryPredicate::Clone() (src/query_predicates.cc:34) returns new QueryPredicate(symbol_, member_name_, value_) — a base QueryPredicate, not a Like. Like does not override Clone().
  • BinaryPredicate (used by And / Or) stores left.Clone() / right.Clone(), so a Like combined with And/Or is downgraded to a base QueryPredicate before Evaluate() runs. A Like-only Evaluate() override would therefore emit ... LIKE ? (no ESCAPE) for every compound predicate, while the bound value is still escaped — i.e. broken matching.

Make the ESCAPE clause travel with the cloned state. Simplest options:

  • have QueryPredicate::Evaluate() append ESCAPE '\' when symbol_ == "LIKE" (both symbol_ and the escaped value_ are copied by Clone()), or
  • give QueryPredicate an optional trailing-clause/suffix field that Clone() copies and Like sets.

Either way, add a test that a Like inside And/Or still matches literally, to lock in the clone-safe behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions