Escape LIKE wildcards in Milvus containsString filter (match #5522/#5553) (#5600)
`MilvusMetadataFilterMapper.mapContains` builds a `ContainsString` query
as `field LIKE "%" + value + "%"` but never escapes the user value's own
LIKE wildcards. In Milvus, `%` and `_` are wildcards, so
`containsString("50%")` matches "50" + anything and
`containsString("a_b")` matches "axb" — but the contract is a literal
substring (`ContainsString#test` uses `String#contains`).
Parent PR #5577 hardened the same mapper's `formatValue` against
string-literal breakout but left the wildcard side undone. The identical
defect was already fixed in the Hibernate (#5522) and MongoDB (#5553)
mappers; Milvus is the store the sweep missed.
The fix adds a `formatLikePattern` that escapes `\`, `%`, `_`, `"` (the
existing `formatValue` is left untouched — its backslash-doubling would
corrupt `\%`). +4 regression tests. Verified by execution (Java 21): the
wildcard cases fail on the old code and pass after; non-wildcard values
are byte-identical.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d2e7af569b
commit
e3ded475a9
|
|
@ -46,10 +46,29 @@ class MilvusMetadataFilterMapper {
|
|||
}
|
||||
|
||||
private static String mapContains(ContainsString containsString, String metadataFieldName) {
|
||||
// ContainsString is a literal substring match (see Filter / ContainsString#test, which uses
|
||||
// String#contains). Milvus LIKE treats % and _ as wildcards, so any % or _ in the user-supplied
|
||||
// value must be escaped to be matched literally; only the surrounding % we add are real wildcards.
|
||||
return format(
|
||||
"%s LIKE %s",
|
||||
formatKey(containsString.key(), metadataFieldName),
|
||||
formatValue("%" + containsString.comparisonValue() + "%"));
|
||||
formatLikePattern(containsString.comparisonValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a quoted Milvus LIKE pattern that matches the given value as a literal substring. The value's
|
||||
* own LIKE wildcards ({@code %} and {@code _}) are escaped with a backslash so they are matched
|
||||
* literally, while the surrounding {@code %} characters remain wildcards for the "contains" semantics.
|
||||
* Backslash and double quote are escaped as in {@link #formatValue(Object)} so the value stays inside
|
||||
* the string literal.
|
||||
*/
|
||||
private static String formatLikePattern(String value) {
|
||||
// Escape backslash first, then the LIKE wildcards % and _, then the string-literal double quote.
|
||||
String escaped = value.replace("\\", "\\\\")
|
||||
.replace("%", "\\%")
|
||||
.replace("_", "\\_")
|
||||
.replace("\"", "\\\"");
|
||||
return "\"%" + escaped + "%\"";
|
||||
}
|
||||
|
||||
private static String mapEqual(IsEqualTo isEqualTo, String metadataFieldName) {
|
||||
|
|
|
|||
|
|
@ -43,4 +43,42 @@ class MilvusMetadataFilterMapperTest {
|
|||
|
||||
assertThat(expr).isEqualTo("metadata[\"key\"] == \"foo\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void contains_should_wrap_plain_value_in_like_wildcards() {
|
||||
Filter filter = metadataKey("key").containsString("foo");
|
||||
|
||||
String expr = MilvusMetadataFilterMapper.map(filter, "metadata");
|
||||
|
||||
assertThat(expr).isEqualTo("metadata[\"key\"] LIKE \"%foo%\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void contains_should_escape_percent_wildcard_in_value() {
|
||||
// "50%" must be matched as a literal substring; the user's '%' must NOT act as a LIKE wildcard.
|
||||
Filter filter = metadataKey("key").containsString("50%");
|
||||
|
||||
String expr = MilvusMetadataFilterMapper.map(filter, "metadata");
|
||||
|
||||
assertThat(expr).isEqualTo("metadata[\"key\"] LIKE \"%50\\%%\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void contains_should_escape_underscore_wildcard_in_value() {
|
||||
// "a_b" must be matched literally; the user's '_' must NOT act as a single-character wildcard.
|
||||
Filter filter = metadataKey("key").containsString("a_b");
|
||||
|
||||
String expr = MilvusMetadataFilterMapper.map(filter, "metadata");
|
||||
|
||||
assertThat(expr).isEqualTo("metadata[\"key\"] LIKE \"%a\\_b%\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void contains_should_escape_backslash_percent_and_underscore_together() {
|
||||
Filter filter = metadataKey("key").containsString("a\\b100%_done");
|
||||
|
||||
String expr = MilvusMetadataFilterMapper.map(filter, "metadata");
|
||||
|
||||
assertThat(expr).isEqualTo("metadata[\"key\"] LIKE \"%a\\\\b100\\%\\_done%\"");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue