Repository navigation
Escape regex metacharacters in Postgres LIKE filter values - #331
sanjulksharma wants to merge 4 commits into
Conversation
Postgres's `~*` operator (used to implement the LIKE/"contains" filter)
is a case-insensitive, unanchored regex match using POSIX ARE, whereas
MongoDB's $regex uses PCRE2. PCRE2 tolerates malformed patterns (e.g. an
unmatched quantifier brace like "{{") by treating them literally, while
Postgres's ARE engine hard-errors with "invalid regular expression:
quantifier operand invalid" on the same input - breaking LIKE/"contains"
filters on Postgres for values containing regex metacharacters (e.g. API
Inventory domain search values like "{{" or "${${8gs").
Add a validity gate (PostgresUtils#sanitizeRegexValue) that passes a LIKE
value through unchanged if it is already a valid regex (preserving
existing regex-filter semantics, e.g. ".*amp.*", "^Sun"), and otherwise
escapes POSIX ARE metacharacters so the value is matched literally
instead of erroring out. Apply this in both LIKE code paths: the legacy
Filter/Op parser and the v1 expression parser
(PostgresLikeRelationalFilterParser), the latter being the actual path
hit by the reported production stacktrace.
Also drop the now-redundant ".*<value>.*" wrapping previously applied to
LIKE values before the ~* search, since `~*` is already an unanchored
substring regex match.
Note: for a value that is not a valid regex in either engine but IS
syntactically valid Perl/PCRE2 (e.g. "${${8gs", which PCRE2 compiles but
matches 0 documents because "$" is treated as an end-of-line anchor),
this introduces a narrow Mongo/Postgres result divergence (Mongo: 0
matches, Postgres: 1 literal match) - a deliberate, documented trade-off
versus leaving Postgres's LIKE operator broken for such inputs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AI-Session-Id: f9a3360e-ecb3-4388-beeb-682ed3b1d400
AI-Tool: claude-code
AI-Model: unknown
The LIKE-regex test added a "notes" field to the shared collection_data.json document (id=8), which broke unrelated tests asserting exact document equality against that fixture (testQueryV1ForSimpleWhereClause, testQueryV1ForFilterWithLogicalExpressionAndOr). Upsert a standalone document scoped to the test instead of mutating shared fixture data. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> AI-Session-Id: f9a3360e-ecb3-4388-beeb-682ed3b1d400 AI-Tool: claude-code AI-Model: unknown
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #331 +/- ##
============================================
+ Coverage 81.23% 81.36% +0.13%
- Complexity 1698 1703 +5
============================================
Files 243 243
Lines 7780 7793 +13
Branches 771 773 +2
============================================
+ Hits 6320 6341 +21
+ Misses 962 955 -7
+ Partials 498 497 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> AI-Session-Id: f9a3360e-ecb3-4388-beeb-682ed3b1d400 AI-Tool: claude-code AI-Model: unknown
Upserting the notes-only doc into the shared collection changed its total document count, breaking unrelated tests that assert exact counts (testFlatVsNestedCollectionConsistency, testNotContains, etc.). Delete it in a finally block so the shared fixture's state is unchanged afterward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> AI-Session-Id: f9a3360e-ecb3-4388-beeb-682ed3b1d400 AI-Tool: claude-code AI-Model: unknown
suresh-prakash
left a comment
There was a problem hiding this comment.
Summary
Reviewed 3 findings. Posted 3 inline comments.
| Severity | Count |
|---|---|
| High | 1 |
| Medium | 2 |
| Low | 0 |
Reviewed using @claude traceable:traceable-java-code-review
💡 Want this skill on your own PRs? Install the Traceable plugin from the Harness internal marketplace:
/plugin marketplace add https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/claude-code-marketplace.git /plugin install traceable@harness-internal-claude-pluginsThen run
/skill traceable:traceable-java-code-review <pr-url>.
| public static String sanitizeRegexValue(final Object value) { | ||
| final String stringValue = String.valueOf(value); | ||
| try { | ||
| Pattern.compile(stringValue); |
There was a problem hiding this comment.
[high] Java regex validation does not validate PostgreSQL ARE or MongoDB PCRE2
`Pattern.compile` validates Java regex syntax, but the value is executed by PostgreSQL's ARE engine while MongoDB uses PCRE2. For example, Java accepts `C++` (possessive quantifier) and `a{256}`, so both pass this gate unchanged, but PostgreSQL rejects `++` and only permits repetition bounds through 255; the query can still fail with `invalid regular expression`. In the opposite direction, PostgreSQL documents `{` followed by a non-digit as a literal brace, so `foo.{bar` is a valid PostgreSQL pattern matching `fooX{bar`; Java rejects it and this code converts the whole pattern to the literal `foo\.\{bar`, changing the match result. Because MongoDB generally accepts the original PCRE pattern, this also creates cross-store incompatibility; please define and validate a common supported regex subset (or consistently treat LIKE as literal search in both backends) instead of using the Java engine as the compatibility gate.Tip
The same input passes through three different grammars: Java Pattern, PostgreSQL ARE, and MongoDB PCRE2. Validity in one is not evidence of validity or equivalent semantics in either of the others. Compatibility tests should execute representative patterns against both datastore implementations and assert the same results, including C++, a{256}, foo.{bar, ^Sun, and b.*B.
References:
Reviewed using @claude traceable:traceable-java-code-review
| Pattern.compile(stringValue); | ||
| return stringValue; | ||
| } catch (final PatternSyntaxException e) { | ||
| return escapeRegexMetacharacters(stringValue); |
There was a problem hiding this comment.
[medium] Use PostgreSQL's documented literal-pattern mode for a literal fallback
If the intended fallback is literal matching, PostgreSQL already documents `***=` (or a leading `(?q)`) to quote the rest of an ARE. For example, binding `***=${${8gs` makes `$`, `{`, and the remaining characters ordinary, while `***=C++` safely matches literal `C++`; this avoids maintaining a context-sensitive metacharacter list and correctly handles backslashes and bracket expressions. This only makes the PostgreSQL fallback safer—it does not by itself provide MongoDB parity, because Mongo may successfully interpret the original value as a PCRE regex—so the fallback policy still needs to be shared by both backends or unsupported patterns should be rejected.Tip
PostgreSQL's ARE metasyntax defines the ***= director as “the rest of the RE is taken to be a literal string, with all characters considered ordinary characters.” Since the pattern is bound through a prepared-statement parameter, the prefix and backslashes are regex content rather than SQL-literal syntax.
References:
Reviewed using @claude traceable:traceable-java-code-review
| // which does hit. This divergence is expected, not a bug. | ||
| long dollarBraceCount = collection.count(dollarBraceQuery); | ||
| if (MONGO_STORE.equals(dataStoreName)) { | ||
| assertEquals(0, dollarBraceCount); |
There was a problem hiding this comment.
[medium] Do not encode different MongoDB and PostgreSQL results as expected behavior
This test explicitly accepts MongoDB returning 0 while PostgreSQL returns 1 for the same document and LIKE value. With document text `weird {{bracket}} and ${${8gs} value`, MongoDB/PCRE2 interprets the `$` characters as end-of-string assertions and does not match, whereas the PostgreSQL fallback turns the expression into literal `\$\{\$\{8gs` and finds that substring. If datastore compatibility is required, this should assert the same result for both providers; either quote the value for both backends when the contract is literal substring search, or define a common regex subset and reject/translate expressions that cannot have equivalent semantics.Tip
A regression test that intentionally branches by datastore makes the incompatibility permanent. Add parity cases for malformed and engine-specific expressions alongside existing valid expressions such as ^Sun, Sha.*, and b.*B, then assert one shared expected result.
References:
Reviewed using @claude traceable:traceable-java-code-review
Fixes ASP-3514: a
LIKEfilter value containing regex metacharacters (e.g.{{) works on MongoDB but throwsinvalid regular expression: quantifier operand invalidon Postgres, since Postgres's~*(POSIX ARE) hard-errors on malformed patterns that MongoDB's PCRE2 tolerates. Adds a validity gate that escapes such values to match literally instead of erroring, applied to both the legacy and v1 LIKE parsers, and drops the now-redundant.*wrapping since~*is already unanchored.(Supersedes #330, which was opened from a fork before I had collaborator access and couldn't run CI.)
🤖 Generated with Claude Code