Skip to content

Commit 37b4611

Browse files
authoredJan 10, 2025··
support termQueryCaseInsensitive/termQuery can search from doc_value in flat_object/keyword field (#16991)
Signed-off-by: kkewwei <kewei.11@bytedance.com> Signed-off-by: kkewwei <kkewwei@163.com>

File tree

7 files changed

+436
-42
lines changed

7 files changed

+436
-42
lines changed
 

‎CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
2222
- Added a precaution to handle extreme date values during sorting to prevent `arithmetic_exception: long overflow` ([#16812](https://github.com/opensearch-project/OpenSearch/pull/16812)).
2323
- Add search replica stats to segment replication stats API ([#16678](https://github.com/opensearch-project/OpenSearch/pull/16678))
2424
- Introduce framework for auxiliary transports and an experimental gRPC transport plugin ([#16534](https://github.com/opensearch-project/OpenSearch/pull/16534))
25+
- Support searching from doc_value using termQueryCaseInsensitive/termQuery in flat_object/keyword field([#16974](https://github.com/opensearch-project/OpenSearch/pull/16974/))
2526

2627
### Dependencies
2728
- Bump `com.google.cloud:google-cloud-core-http` from 2.23.0 to 2.47.0 ([#16504](https://github.com/opensearch-project/OpenSearch/pull/16504))

‎rest-api-spec/src/main/resources/rest-api-spec/test/index/92_flat_object_support_doc_values.yml

+49-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ setup:
4444
{"order":"order7","issue":{"labels":{"number":7,"name":"abc7","status":1}}}
4545
{"index":{"_index":"flat_object_doc_values_test","_id":"8"}}
4646
{"order":"order8","issue":{"labels":{"number":8,"name":"abc8","status":1}}}
47+
{"index":{"_index":"flat_object_doc_values_test","_id":"9"}}
48+
{"order":"order9","issue":{"labels":{"number":9,"name":"abC8","status":1}}}
4749
4850
---
4951
# Delete Index when connection is teardown
@@ -67,7 +69,53 @@ teardown:
6769
}
6870
}
6971

70-
- length: { hits.hits: 9 }
72+
- length: { hits.hits: 10 }
73+
74+
# Case Insensitive Term Query with exact dot path.
75+
- do:
76+
search:
77+
body: {
78+
_source: true,
79+
query: {
80+
bool: {
81+
must: [
82+
{
83+
term: {
84+
issue.labels.name: {
85+
value: "abc8",
86+
case_insensitive: "true"
87+
}
88+
}
89+
}
90+
]
91+
}
92+
}
93+
}
94+
95+
- length: { hits.hits: 2 }
96+
97+
# Case Insensitive Term Query with no path.
98+
- do:
99+
search:
100+
body: {
101+
_source: true,
102+
query: {
103+
bool: {
104+
must: [
105+
{
106+
term: {
107+
issue.labels: {
108+
value: "abc8",
109+
case_insensitive: "true"
110+
}
111+
}
112+
}
113+
]
114+
}
115+
}
116+
}
117+
118+
- length: { hits.hits: 2 }
71119

72120
# Term Query with exact dot path.
73121
- do:

‎rest-api-spec/src/main/resources/rest-api-spec/test/search/340_doc_values_field.yml

+33-2
Original file line numberDiff line numberDiff line change
@@ -1126,8 +1126,8 @@ setup:
11261126
"search on fields with only doc_values enabled":
11271127
- skip:
11281128
features: [ "headers" ]
1129-
version: " - 2.18.99"
1130-
reason: "searching with only doc_values was finally added in 2.19.0"
1129+
version: " - 2.99.99"
1130+
reason: "searching with only doc_values was finally added in 3.0.0"
11311131
- do:
11321132
indices.create:
11331133
index: test-doc-values
@@ -1198,6 +1198,37 @@ setup:
11981198
- '{ "some_keyword": "400", "byte": 121, "double": 101.0, "float": "801.0", "half_float": "401.0", "integer": 1291, "long": 13457, "short": 151, "unsigned_long": 10223372036854775801, "ip_field": "192.168.0.2", "boolean": true, "date_nanos": "2020-10-29T12:12:12.123456789Z", "date": "2020-10-29T12:12:12.987Z" }'
11991199
- '{ "index": { "_index": "test-doc-values", "_id": "3" } }'
12001200
- '{ "some_keyword": "5", "byte": 122, "double": 102.0, "float": "802.0", "half_float": "402.0", "integer": 1292, "long": 13458, "short": 152, "unsigned_long": 10223372036854775802, "ip_field": "192.168.0.3", "boolean": false, "date_nanos": "2024-10-29T12:12:12.123456789Z", "date": "2024-10-29T12:12:12.987Z" }'
1201+
- '{ "index": { "_index": "test-doc-values", "_id": "4" } }'
1202+
- '{ "some_keyword": "Keyword1" }'
1203+
- '{ "index": { "_index": "test-doc-values", "_id": "5" } }'
1204+
- '{ "some_keyword": "keyword1" }'
1205+
1206+
- do:
1207+
search:
1208+
rest_total_hits_as_int: true
1209+
index: test-doc-values
1210+
body:
1211+
query:
1212+
term: {
1213+
"some_keyword": {
1214+
"value": "Keyword1"
1215+
} }
1216+
1217+
- match: { hits.total: 1 }
1218+
1219+
- do:
1220+
search:
1221+
rest_total_hits_as_int: true
1222+
index: test-doc-values
1223+
body:
1224+
query:
1225+
term: {
1226+
"some_keyword": {
1227+
"value": "keyword1",
1228+
"case_insensitive": "true"
1229+
} }
1230+
1231+
- match: { hits.total: 2 }
12011232

12021233
- do:
12031234
search:

‎server/src/main/java/org/opensearch/index/mapper/FlatObjectFieldMapper.java

+6-13
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import org.apache.lucene.document.SortedSetDocValuesField;
1616
import org.apache.lucene.index.IndexOptions;
1717
import org.apache.lucene.index.Term;
18-
import org.apache.lucene.search.BoostQuery;
1918
import org.apache.lucene.search.FieldExistsQuery;
2019
import org.apache.lucene.search.MultiTermQuery;
2120
import org.apache.lucene.search.Query;
@@ -336,23 +335,17 @@ private KeywordFieldType valueFieldType() {
336335
return (mappedFieldTypeName == null) ? valueFieldType : valueAndPathFieldType;
337336
}
338337

338+
@Override
339+
public Query termQueryCaseInsensitive(Object value, QueryShardContext context) {
340+
return valueFieldType().termQueryCaseInsensitive(rewriteValue(inputToString(value)), context);
341+
}
342+
339343
/**
340344
* redirect queries with rewrite value to rewriteSearchValue and directSubFieldName
341345
*/
342346
@Override
343347
public Query termQuery(Object value, @Nullable QueryShardContext context) {
344-
345-
String searchValueString = inputToString(value);
346-
String directSubFieldName = directSubfield();
347-
String rewriteSearchValue = rewriteValue(searchValueString);
348-
349-
failIfNotIndexed();
350-
Query query;
351-
query = new TermQuery(new Term(directSubFieldName, indexedValueForSearch(rewriteSearchValue)));
352-
if (boost() != 1f) {
353-
query = new BoostQuery(query, boost());
354-
}
355-
return query;
348+
return valueFieldType().termQuery(rewriteValue(inputToString(value)), context);
356349
}
357350

358351
@Override

‎server/src/main/java/org/opensearch/index/mapper/KeywordFieldMapper.java

+41
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.apache.lucene.document.SortedSetDocValuesField;
4040
import org.apache.lucene.index.IndexOptions;
4141
import org.apache.lucene.index.Term;
42+
import org.apache.lucene.search.BoostQuery;
4243
import org.apache.lucene.search.FuzzyQuery;
4344
import org.apache.lucene.search.IndexOrDocValuesQuery;
4445
import org.apache.lucene.search.MultiTermQuery;
@@ -391,6 +392,46 @@ protected Object rewriteForDocValue(Object value) {
391392
return value;
392393
}
393394

395+
@Override
396+
public Query termQueryCaseInsensitive(Object value, QueryShardContext context) {
397+
failIfNotIndexedAndNoDocValues();
398+
if (isSearchable()) {
399+
return super.termQueryCaseInsensitive(value, context);
400+
} else {
401+
BytesRef bytesRef = indexedValueForSearch(rewriteForDocValue(value));
402+
Term term = new Term(name(), bytesRef);
403+
Query query = AutomatonQueries.createAutomatonQuery(
404+
term,
405+
AutomatonQueries.toCaseInsensitiveString(bytesRef.utf8ToString(), Operations.DEFAULT_DETERMINIZE_WORK_LIMIT),
406+
MultiTermQuery.DOC_VALUES_REWRITE
407+
);
408+
if (boost() != 1f) {
409+
query = new BoostQuery(query, boost());
410+
}
411+
return query;
412+
}
413+
}
414+
415+
@Override
416+
public Query termQuery(Object value, QueryShardContext context) {
417+
failIfNotIndexedAndNoDocValues();
418+
if (isSearchable()) {
419+
return super.termQuery(value, context);
420+
} else {
421+
Query query = SortedSetDocValuesField.newSlowRangeQuery(
422+
name(),
423+
indexedValueForSearch(rewriteForDocValue(value)),
424+
indexedValueForSearch(rewriteForDocValue(value)),
425+
true,
426+
true
427+
);
428+
if (boost() != 1f) {
429+
query = new BoostQuery(query, boost());
430+
}
431+
return query;
432+
}
433+
}
434+
394435
@Override
395436
public Query termsQuery(List<?> values, QueryShardContext context) {
396437
failIfNotIndexedAndNoDocValues();

‎server/src/test/java/org/opensearch/index/mapper/FlatObjectFieldTypeTests.java

+261-22
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
package org.opensearch.index.mapper;
1010

1111
import org.apache.lucene.document.FieldType;
12+
import org.apache.lucene.document.SortedSetDocValuesField;
1213
import org.apache.lucene.index.IndexOptions;
1314
import org.apache.lucene.index.Term;
1415
import org.apache.lucene.search.FieldExistsQuery;
@@ -24,6 +25,7 @@
2425
import org.apache.lucene.search.WildcardQuery;
2526
import org.apache.lucene.util.BytesRef;
2627
import org.apache.lucene.util.automaton.Operations;
28+
import org.opensearch.common.lucene.search.AutomatonQueries;
2729
import org.opensearch.common.unit.Fuzziness;
2830
import org.opensearch.index.analysis.AnalyzerScope;
2931
import org.opensearch.index.analysis.NamedAnalyzer;
@@ -138,47 +140,284 @@ public void testRewriteValue() {
138140
assertEquals("field.bar=foo", searchValuesDocPath);
139141
}
140142

141-
public void testTermQuery() {
143+
public void testTermQueryCaseInsensitive() {
142144

143-
FlatObjectFieldMapper.FlatObjectFieldType flatParentFieldType = (FlatObjectFieldMapper.FlatObjectFieldType) getFlatParentFieldType(
145+
// 1.test isSearchable=true, hasDocValues=true, mappedFieldTypeName=null
146+
{
147+
FlatObjectFieldMapper.FlatObjectFieldType flatParentFieldType =
148+
(FlatObjectFieldMapper.FlatObjectFieldType) getFlatParentFieldType("field", null, true, true);
149+
150+
MappedFieldType dynamicMappedFieldType = new FlatObjectFieldMapper.FlatObjectFieldType(
151+
"field.bar",
152+
flatParentFieldType.name(),
153+
flatParentFieldType.getValueFieldType(),
154+
flatParentFieldType.getValueAndPathFieldType()
155+
);
156+
assertEquals(
157+
AutomatonQueries.caseInsensitiveTermQuery(new Term("field._valueAndPath", "field.bar=fOo")),
158+
dynamicMappedFieldType.termQueryCaseInsensitive("fOo", null)
159+
);
160+
}
161+
162+
// 2.test isSearchable=true, hasDocValues=false, mappedFieldTypeName=null
163+
{
164+
FlatObjectFieldMapper.FlatObjectFieldType ft = (FlatObjectFieldMapper.FlatObjectFieldType) getFlatParentFieldType(
165+
"field",
166+
null,
167+
true,
168+
false
169+
);
170+
assertEquals(
171+
AutomatonQueries.caseInsensitiveTermQuery(new Term("field._value", "fOo")),
172+
ft.termQueryCaseInsensitive("fOo", null)
173+
);
174+
}
175+
176+
// test isSearchable=true, hasDocValues=false, mappedFieldTypeName!=null
177+
{
178+
FlatObjectFieldMapper.FlatObjectFieldType ft = (FlatObjectFieldMapper.FlatObjectFieldType) getFlatParentFieldType(
179+
"field",
180+
"field",
181+
true,
182+
false
183+
);
184+
Query expected = new TermQuery(new Term("field" + VALUE_AND_PATH_SUFFIX, new BytesRef("fOo")));
185+
186+
assertEquals(expected, ft.termQuery("fOo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES));
187+
}
188+
189+
// 3.test isSearchable=false, hasDocValues=true, mappedFieldTypeName=null
190+
{
191+
FlatObjectFieldMapper.FlatObjectFieldType ft = (FlatObjectFieldMapper.FlatObjectFieldType) getFlatParentFieldType(
192+
"field",
193+
null,
194+
false,
195+
true
196+
);
197+
Query expected = AutomatonQueries.createAutomatonQuery(
198+
new Term("field" + VALUE_SUFFIX, "field.fOo"),
199+
AutomatonQueries.toCaseInsensitiveString("field.fOo", Integer.MAX_VALUE),
200+
MultiTermQuery.DOC_VALUES_REWRITE
201+
);
202+
assertEquals(expected, ft.termQueryCaseInsensitive("fOo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES));
203+
}
204+
205+
// test isSearchable=false, hasDocValues=true, mappedFieldTypeName!=null
206+
{
207+
FlatObjectFieldMapper.FlatObjectFieldType ft = (FlatObjectFieldMapper.FlatObjectFieldType) getFlatParentFieldType(
208+
"field",
209+
"field",
210+
false,
211+
true
212+
);
213+
Query expected = AutomatonQueries.createAutomatonQuery(
214+
new Term("field" + VALUE_AND_PATH_SUFFIX, "field.fOo"),
215+
AutomatonQueries.toCaseInsensitiveString("field.fOo", Integer.MAX_VALUE),
216+
MultiTermQuery.DOC_VALUES_REWRITE
217+
);
218+
219+
assertEquals(expected, ft.termQueryCaseInsensitive("fOo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES));
220+
}
221+
222+
// 4.test isSearchable=false, hasDocValues=false, mappedFieldTypeName=null
223+
{
224+
FlatObjectFieldMapper.FlatObjectFieldType ft = (FlatObjectFieldMapper.FlatObjectFieldType) getFlatParentFieldType(
225+
"field",
226+
null,
227+
false,
228+
false
229+
);
230+
IllegalArgumentException e = expectThrows(
231+
IllegalArgumentException.class,
232+
() -> ft.termQueryCaseInsensitive("foo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES)
233+
);
234+
assertEquals(
235+
"Cannot search on field [field._value] since it is both not indexed, and does not have doc_values " + "enabled.",
236+
e.getMessage()
237+
);
238+
}
239+
240+
// test isSearchable=false, hasDocValues=false, mappedFieldTypeName!=null
241+
{
242+
FlatObjectFieldMapper.FlatObjectFieldType ft = (FlatObjectFieldMapper.FlatObjectFieldType) getFlatParentFieldType(
243+
"field",
244+
"field",
245+
false,
246+
false
247+
);
248+
IllegalArgumentException e = expectThrows(
249+
IllegalArgumentException.class,
250+
() -> ft.termQuery("foo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES)
251+
);
252+
assertEquals(
253+
"Cannot search on field [field._valueAndPath] since it is both not indexed, and does not have doc_values " + "enabled.",
254+
e.getMessage()
255+
);
256+
}
257+
258+
MappedFieldType unsearchable = new FlatObjectFieldMapper.FlatObjectFieldType(
144259
"field",
145260
null,
146-
true,
147-
true
261+
false,
262+
false,
263+
null,
264+
Collections.emptyMap()
265+
);
266+
IllegalArgumentException e = expectThrows(
267+
IllegalArgumentException.class,
268+
() -> unsearchable.termQuery("bar", MOCK_QSC_ENABLE_INDEX_DOC_VALUES)
148269
);
270+
assertEquals(
271+
"Cannot search on field [field._value] since it is both not indexed, and does not have doc_values enabled.",
272+
e.getMessage()
273+
);
274+
}
149275

150-
// when searching for "foo" in "field", the term query is directed to search "foo" in field._value field
151-
String searchFieldName = (flatParentFieldType).directSubfield();
152-
String searchValues = (flatParentFieldType).rewriteValue("foo");
153-
assertEquals("foo", searchValues);
154-
assertEquals(new TermQuery(new Term(searchFieldName, searchValues)), flatParentFieldType.termQuery(searchValues, null));
276+
public void testTermQuery() {
155277

156-
MappedFieldType dynamicMappedFieldType = new FlatObjectFieldMapper.FlatObjectFieldType(
157-
"field.bar",
158-
flatParentFieldType.name(),
159-
flatParentFieldType.getValueFieldType(),
160-
flatParentFieldType.getValueAndPathFieldType()
161-
);
278+
// 1.test isSearchable=true, hasDocValues=true, mappedFieldTypeName=null
279+
{
280+
FlatObjectFieldMapper.FlatObjectFieldType flatParentFieldType =
281+
(FlatObjectFieldMapper.FlatObjectFieldType) getFlatParentFieldType("field", null, true, true);
162282

163-
// when searching for "foo" in "field.bar", the term query is directed to search in field._valueAndPath field
164-
String searchFieldNameDocPath = ((FlatObjectFieldMapper.FlatObjectFieldType) dynamicMappedFieldType).directSubfield();
165-
String searchValuesDocPath = ((FlatObjectFieldMapper.FlatObjectFieldType) dynamicMappedFieldType).rewriteValue("foo");
166-
assertEquals("field.bar=foo", searchValuesDocPath);
167-
assertEquals(new TermQuery(new Term(searchFieldNameDocPath, searchValuesDocPath)), dynamicMappedFieldType.termQuery("foo", null));
283+
// when searching for "foo" in "field", the term query is directed to search "foo" in field._value field
284+
String searchFieldName = (flatParentFieldType).directSubfield();
285+
String searchValues = (flatParentFieldType).rewriteValue("foo");
286+
assertEquals("foo", searchValues);
287+
assertEquals(new TermQuery(new Term(searchFieldName, searchValues)), flatParentFieldType.termQuery(searchValues, null));
288+
289+
MappedFieldType dynamicMappedFieldType = new FlatObjectFieldMapper.FlatObjectFieldType(
290+
"field.bar",
291+
flatParentFieldType.name(),
292+
flatParentFieldType.getValueFieldType(),
293+
flatParentFieldType.getValueAndPathFieldType()
294+
);
295+
296+
// when searching for "foo" in "field.bar", the term query is directed to search in field._valueAndPath field
297+
String searchFieldNameDocPath = ((FlatObjectFieldMapper.FlatObjectFieldType) dynamicMappedFieldType).directSubfield();
298+
String searchValuesDocPath = ((FlatObjectFieldMapper.FlatObjectFieldType) dynamicMappedFieldType).rewriteValue("foo");
299+
assertEquals("field.bar=foo", searchValuesDocPath);
300+
assertEquals(
301+
new TermQuery(new Term(searchFieldNameDocPath, searchValuesDocPath)),
302+
dynamicMappedFieldType.termQuery("foo", null)
303+
);
304+
305+
}
306+
307+
// 2.test isSearchable=true, hasDocValues=false, mappedFieldTypeName=null
308+
{
309+
FlatObjectFieldMapper.FlatObjectFieldType ft = (FlatObjectFieldMapper.FlatObjectFieldType) getFlatParentFieldType(
310+
"field",
311+
null,
312+
true,
313+
false
314+
);
315+
Query expected = new TermQuery(new Term("field" + VALUE_SUFFIX, new BytesRef("foo")));
316+
assertEquals(expected, ft.termQuery("foo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES));
317+
}
318+
319+
// test isSearchable=true, hasDocValues=false, mappedFieldTypeName!=null
320+
{
321+
FlatObjectFieldMapper.FlatObjectFieldType ft = (FlatObjectFieldMapper.FlatObjectFieldType) getFlatParentFieldType(
322+
"field",
323+
"field",
324+
true,
325+
false
326+
);
327+
Query expected = new TermQuery(new Term("field" + VALUE_AND_PATH_SUFFIX, new BytesRef("foo")));
328+
329+
assertEquals(expected, ft.termQuery("foo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES));
330+
}
331+
332+
// 3.test isSearchable=false, hasDocValues=true, mappedFieldTypeName=null
333+
{
334+
FlatObjectFieldMapper.FlatObjectFieldType ft = (FlatObjectFieldMapper.FlatObjectFieldType) getFlatParentFieldType(
335+
"field",
336+
null,
337+
false,
338+
true
339+
);
340+
Query expected = SortedSetDocValuesField.newSlowRangeQuery(
341+
"field" + VALUE_SUFFIX,
342+
new BytesRef("field.foo"),
343+
new BytesRef("field.foo"),
344+
true,
345+
true
346+
);
347+
assertEquals(expected, ft.termQuery("foo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES));
348+
349+
}
350+
351+
// test isSearchable=false, hasDocValues=true, mappedFieldTypeName!=null
352+
{
353+
FlatObjectFieldMapper.FlatObjectFieldType ft = (FlatObjectFieldMapper.FlatObjectFieldType) getFlatParentFieldType(
354+
"field",
355+
"field",
356+
false,
357+
true
358+
);
359+
Query expected = SortedSetDocValuesField.newSlowRangeQuery(
360+
"field" + VALUE_AND_PATH_SUFFIX,
361+
new BytesRef("field.foo"),
362+
new BytesRef("field.foo"),
363+
true,
364+
true
365+
);
366+
assertEquals(expected, ft.termQuery("foo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES));
367+
}
368+
369+
// 4.test isSearchable=false, hasDocValues=false, mappedFieldTypeName=null
370+
{
371+
FlatObjectFieldMapper.FlatObjectFieldType ft = (FlatObjectFieldMapper.FlatObjectFieldType) getFlatParentFieldType(
372+
"field",
373+
null,
374+
false,
375+
false
376+
);
377+
IllegalArgumentException e = expectThrows(
378+
IllegalArgumentException.class,
379+
() -> ft.termQuery("foo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES)
380+
);
381+
assertEquals(
382+
"Cannot search on field [field._value] since it is both not indexed, and does not have doc_values " + "enabled.",
383+
e.getMessage()
384+
);
385+
}
386+
387+
// test isSearchable=false, hasDocValues=false, mappedFieldTypeName!=null
388+
{
389+
FlatObjectFieldMapper.FlatObjectFieldType ft = (FlatObjectFieldMapper.FlatObjectFieldType) getFlatParentFieldType(
390+
"field",
391+
"field",
392+
false,
393+
false
394+
);
395+
IllegalArgumentException e = expectThrows(
396+
IllegalArgumentException.class,
397+
() -> ft.termQuery("foo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES)
398+
);
399+
assertEquals(
400+
"Cannot search on field [field._valueAndPath] since it is both not indexed, and does not have doc_values " + "enabled.",
401+
e.getMessage()
402+
);
403+
}
168404

169405
MappedFieldType unsearchable = new FlatObjectFieldMapper.FlatObjectFieldType(
170406
"field",
171407
null,
172408
false,
173-
true,
409+
false,
174410
null,
175411
Collections.emptyMap()
176412
);
177413
IllegalArgumentException e = expectThrows(
178414
IllegalArgumentException.class,
179415
() -> unsearchable.termQuery("bar", MOCK_QSC_ENABLE_INDEX_DOC_VALUES)
180416
);
181-
assertEquals("Cannot search on field [field] since it is not indexed.", e.getMessage());
417+
assertEquals(
418+
"Cannot search on field [field._value] since it is both not indexed, and does not have doc_values enabled.",
419+
e.getMessage()
420+
);
182421
}
183422

184423
public void testExistsQuery() {

‎server/src/test/java/org/opensearch/index/mapper/KeywordFieldTypeTests.java

+45-4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.apache.lucene.analysis.core.WhitespaceTokenizer;
4242
import org.apache.lucene.analysis.standard.StandardAnalyzer;
4343
import org.apache.lucene.document.FieldType;
44+
import org.apache.lucene.document.SortedSetDocValuesField;
4445
import org.apache.lucene.index.Term;
4546
import org.apache.lucene.search.DocValuesFieldExistsQuery;
4647
import org.apache.lucene.search.FuzzyQuery;
@@ -60,6 +61,7 @@
6061
import org.opensearch.cluster.metadata.IndexMetadata;
6162
import org.opensearch.common.lucene.BytesRefs;
6263
import org.opensearch.common.lucene.Lucene;
64+
import org.opensearch.common.lucene.search.AutomatonQueries;
6365
import org.opensearch.common.settings.Settings;
6466
import org.opensearch.common.unit.Fuzziness;
6567
import org.opensearch.index.analysis.AnalyzerScope;
@@ -100,13 +102,52 @@ public void testIsFieldWithinQuery() throws IOException {
100102
);
101103
}
102104

105+
public void testTermQueryCaseInsensitive() {
106+
MappedFieldType ft = new KeywordFieldType("field");
107+
Query expected = AutomatonQueries.caseInsensitiveTermQuery(new Term("field", BytesRefs.toBytesRef("foo")));
108+
assertEquals(expected, ft.termQueryCaseInsensitive("foo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES));
109+
110+
ft = new KeywordFieldType("field", true, false, Collections.emptyMap());
111+
assertEquals(expected, ft.termQueryCaseInsensitive("foo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES));
112+
113+
ft = new KeywordFieldType("field", false, true, Collections.emptyMap());
114+
Term term = new Term("field", "foo");
115+
116+
expected = AutomatonQueries.createAutomatonQuery(
117+
term,
118+
AutomatonQueries.toCaseInsensitiveString("foo", Integer.MAX_VALUE),
119+
MultiTermQuery.DOC_VALUES_REWRITE
120+
);
121+
assertEquals(expected, ft.termQueryCaseInsensitive("foo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES));
122+
123+
MappedFieldType unsearchable = new KeywordFieldType("field", false, false, Collections.emptyMap());
124+
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> unsearchable.termQueryCaseInsensitive("foo", null));
125+
assertEquals(
126+
"Cannot search on field [field] since it is both not indexed, and does not have doc_values " + "enabled.",
127+
e.getMessage()
128+
);
129+
}
130+
103131
public void testTermQuery() {
104132
MappedFieldType ft = new KeywordFieldType("field");
105-
assertEquals(new TermQuery(new Term("field", "foo")), ft.termQuery("foo", null));
133+
assertEquals(new TermQuery(new Term("field", "foo")), ft.termQuery("foo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES));
134+
135+
ft = new KeywordFieldType("field", true, false, Collections.emptyMap());
136+
assertEquals(new TermQuery(new Term("field", "foo")), ft.termQuery("foo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES));
106137

107-
MappedFieldType unsearchable = new KeywordFieldType("field", false, true, Collections.emptyMap());
108-
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> unsearchable.termQuery("bar", null));
109-
assertEquals("Cannot search on field [field] since it is not indexed.", e.getMessage());
138+
ft = new KeywordFieldType("field", false, true, Collections.emptyMap());
139+
Query expected = SortedSetDocValuesField.newSlowRangeQuery("field", new BytesRef("foo"), new BytesRef("foo"), true, true);
140+
assertEquals(expected, ft.termQuery("foo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES));
141+
142+
MappedFieldType unsearchable = new KeywordFieldType("field", false, false, Collections.emptyMap());
143+
IllegalArgumentException e = expectThrows(
144+
IllegalArgumentException.class,
145+
() -> unsearchable.termQuery("foo", MOCK_QSC_ENABLE_INDEX_DOC_VALUES)
146+
);
147+
assertEquals(
148+
"Cannot search on field [field] since it is both not indexed, and does not have doc_values " + "enabled.",
149+
e.getMessage()
150+
);
110151
}
111152

112153
public void testTermQueryWithNormalizer() {

0 commit comments

Comments
 (0)
Please sign in to comment.