Skip to content

Commit 508e908

Browse files
[Bug Fix] Fix the backward compatibility regression with COMPLEMENT for Regexp queries introduced in OpenSearch 3.0 (#18640)
* FIx ~ bug Signed-off-by: Prudhvi Godithi <pgodithi@amazon.com> * Update changelog Signed-off-by: Prudhvi Godithi <pgodithi@amazon.com> --------- Signed-off-by: Prudhvi Godithi <pgodithi@amazon.com> (cherry picked from commit 1af55d2) Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent dc09c5d commit 508e908

File tree

4 files changed

+81
-2
lines changed

4 files changed

+81
-2
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1515
### Removed
1616

1717
### Fixed
18+
- Add task cancellation checks in aggregators ([#18426](https://github.com/opensearch-project/OpenSearch/pull/18426))
19+
- Fix concurrent timings in profiler ([#18540](https://github.com/opensearch-project/OpenSearch/pull/18540))
20+
- Fix regex query from query string query to work with field alias ([#18215](https://github.com/opensearch-project/OpenSearch/issues/18215))
21+
- [Autotagging] Fix delete rule event consumption in InMemoryRuleProcessingService ([#18628](https://github.com/opensearch-project/OpenSearch/pull/18628))
22+
- Cannot communicate with HTTP/2 when reactor-netty is enabled ([#18599](https://github.com/opensearch-project/OpenSearch/pull/18599))
23+
- Fix the visit of sub queries for HasParentQuery and HasChildQuery ([#18621](https://github.com/opensearch-project/OpenSearch/pull/18621))
24+
- Fix the backward compatibility regression with COMPLEMENT for Regexp queries introduced in OpenSearch 3.0 ([#18640](https://github.com/opensearch-project/OpenSearch/pull/18640))
1825

1926
### Security
2027

server/src/internalClusterTest/java/org/opensearch/search/simple/SimpleSearchIT.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
import org.opensearch.index.mapper.MapperService;
5050
import org.opensearch.index.query.ConstantScoreQueryBuilder;
5151
import org.opensearch.index.query.QueryBuilders;
52+
import org.opensearch.index.query.RegexpFlag;
53+
import org.opensearch.index.query.RegexpQueryBuilder;
5254
import org.opensearch.index.query.TermQueryBuilder;
5355
import org.opensearch.search.rescore.QueryRescorerBuilder;
5456
import org.opensearch.search.sort.SortOrder;
@@ -59,7 +61,9 @@
5961
import java.util.Arrays;
6062
import java.util.Collection;
6163
import java.util.List;
64+
import java.util.Set;
6265
import java.util.concurrent.ExecutionException;
66+
import java.util.stream.Collectors;
6367

6468
import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
6569
import static org.opensearch.cluster.metadata.IndexMetadata.SETTING_NUMBER_OF_REPLICAS;
@@ -763,4 +767,19 @@ private void assertRescoreWindowFails(int windowSize) {
763767
)
764768
);
765769
}
770+
771+
public void testRegexQueryWithComplementFlag() {
772+
createIndex("test_regex");
773+
client().prepareIndex("test_regex").setId("1").setSource("text", "abc").get();
774+
client().prepareIndex("test_regex").setId("2").setSource("text", "adc").get();
775+
client().prepareIndex("test_regex").setId("3").setSource("text", "acc").get();
776+
refresh();
777+
RegexpQueryBuilder query = new RegexpQueryBuilder("text.keyword", "a~bc");
778+
query.flags(RegexpFlag.COMPLEMENT);
779+
SearchResponse response = client().prepareSearch("test_regex").setQuery(query).get();
780+
assertEquals("COMPLEMENT should match 2 documents", 2L, response.getHits().getTotalHits().value());
781+
Set<String> matchedIds = Arrays.stream(response.getHits().getHits()).map(hit -> hit.getId()).collect(Collectors.toSet());
782+
assertEquals("Should match exactly 2 documents", 2, matchedIds.size());
783+
assertTrue("Should match documents 2 and 3", matchedIds.containsAll(Arrays.asList("2", "3")));
784+
}
766785
}

server/src/main/java/org/opensearch/index/query/RegexpQueryBuilder.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.apache.lucene.search.RegexpQuery;
3939
import org.apache.lucene.util.automaton.Operations;
4040
import org.apache.lucene.util.automaton.RegExp;
41+
import org.opensearch.common.logging.DeprecationLogger;
4142
import org.opensearch.common.lucene.BytesRefs;
4243
import org.opensearch.common.xcontent.LoggingDeprecationHandler;
4344
import org.opensearch.core.ParseField;
@@ -60,6 +61,9 @@
6061
* @opensearch.internal
6162
*/
6263
public class RegexpQueryBuilder extends AbstractQueryBuilder<RegexpQueryBuilder> implements MultiTermQueryBuilder {
64+
65+
private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RegexpQueryBuilder.class);
66+
6367
public static final String NAME = "regexp";
6468

6569
public static final int DEFAULT_FLAGS_VALUE = RegexpFlag.ALL.value();
@@ -294,12 +298,25 @@ protected Query doToQuery(QueryShardContext context) throws QueryShardException,
294298
+ "] index level setting."
295299
);
296300
}
301+
302+
// Check if COMPLEMENT flag is being used
303+
// The COMPLEMENT flag maps to Lucene's DEPRECATED_COMPLEMENT which is marked for removal in Lucene 11
304+
// This deprecation warning helps users migrate their queries before the feature is completely removed
305+
if ((syntaxFlagsValue & RegexpFlag.COMPLEMENT.value()) != 0) {
306+
deprecationLogger.deprecate(
307+
"regexp_complement_operator",
308+
"The complement operator (~) for arbitrary patterns in regexp queries is deprecated and will be removed in a future version. "
309+
+ "Consider rewriting your query to use character class negation [^...] or other query types."
310+
);
311+
}
312+
297313
MultiTermQuery.RewriteMethod method = QueryParsers.parseRewriteMethod(rewrite, null, LoggingDeprecationHandler.INSTANCE);
298314

299315
int matchFlagsValue = caseInsensitive ? RegExp.ASCII_CASE_INSENSITIVE : 0;
300316
Query query = null;
301317
// For BWC we mask irrelevant bits (RegExp changed ALL from 0xffff to 0xff)
302-
int sanitisedSyntaxFlag = syntaxFlagsValue & RegExp.ALL;
318+
// The hexadecimal for DEPRECATED_COMPLEMENT is 0x10000. The OR condition ensures COMPLEMENT ~ is preserved
319+
int sanitisedSyntaxFlag = syntaxFlagsValue & (RegExp.ALL | RegExp.DEPRECATED_COMPLEMENT);
303320

304321
MappedFieldType fieldType = context.fieldMapper(fieldName);
305322
if (fieldType != null) {

server/src/test/java/org/opensearch/index/query/RegexpQueryBuilderTests.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,15 @@ protected RegexpQueryBuilder doCreateTestQueryBuilder() {
5555
List<RegexpFlag> flags = new ArrayList<>();
5656
int iter = randomInt(5);
5757
for (int i = 0; i < iter; i++) {
58-
flags.add(randomFrom(RegexpFlag.values()));
58+
// Exclude COMPLEMENT from random selection to avoid deprecation warnings
59+
RegexpFlag[] availableFlags = {
60+
RegexpFlag.INTERSECTION,
61+
RegexpFlag.EMPTY,
62+
RegexpFlag.ANYSTRING,
63+
RegexpFlag.INTERVAL,
64+
RegexpFlag.NONE,
65+
RegexpFlag.ALL };
66+
flags.add(randomFrom(availableFlags));
5967
}
6068
query.flags(flags.toArray(new RegexpFlag[0]));
6169
}
@@ -162,4 +170,32 @@ public void testParseFailsWithMultipleFields() throws IOException {
162170
e = expectThrows(ParsingException.class, () -> parseQuery(shortJson));
163171
assertEquals("[regexp] query doesn't support multiple fields, found [user1] and [user2]", e.getMessage());
164172
}
173+
174+
// Test that COMPLEMENT flag triggers deprecation warning
175+
public void testComplementFlagDeprecation() throws IOException {
176+
RegexpQueryBuilder query = new RegexpQueryBuilder("field", "a~bc");
177+
query.flags(RegexpFlag.COMPLEMENT);
178+
QueryShardContext context = createShardContext();
179+
Query luceneQuery = query.toQuery(context);
180+
assertNotNull(luceneQuery);
181+
assertThat(luceneQuery, instanceOf(RegexpQuery.class));
182+
assertWarnings(
183+
"The complement operator (~) for arbitrary patterns in regexp queries is deprecated "
184+
+ "and will be removed in a future version. Consider rewriting your query to use character class negation [^...] or other query types."
185+
);
186+
}
187+
188+
// Separate test for COMPLEMENT flag Cacheability
189+
public void testComplementFlagCacheability() throws IOException {
190+
RegexpQueryBuilder queryBuilder = new RegexpQueryBuilder("field", "pattern");
191+
queryBuilder.flags(RegexpFlag.COMPLEMENT);
192+
QueryShardContext context = createShardContext();
193+
QueryBuilder rewriteQuery = rewriteQuery(queryBuilder, new QueryShardContext(context));
194+
assertNotNull(rewriteQuery.toQuery(context));
195+
assertTrue("query should be cacheable: " + queryBuilder, context.isCacheable());
196+
assertWarnings(
197+
"The complement operator (~) for arbitrary patterns in regexp queries is deprecated "
198+
+ "and will be removed in a future version. Consider rewriting your query to use character class negation [^...] or other query types."
199+
);
200+
}
165201
}

0 commit comments

Comments
 (0)