Skip to content

Flag in _field_caps to return only fields with values in index #103651

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 122 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
c8af341
add has_value to field_caps response
piergm Dec 19, 2023
e17fe2c
Merge branch 'elastic:main' into field_caps-has_value
piergm Dec 20, 2023
feccdc7
working prototype
piergm Dec 20, 2023
5ad8b99
move map to shard instead of index, adds queryparam
piergm Dec 21, 2023
ed3c3c6
rename query param to include_fields_with_no_value
piergm Dec 21, 2023
45d892c
code clean
piergm Dec 21, 2023
21061bb
include_fields_with_no_value defaults to true
piergm Dec 21, 2023
e92e9dc
defaults in tests
piergm Dec 21, 2023
838ebbd
defaults in tests
piergm Dec 21, 2023
f5bfd31
Update docs/changelog/103651.yaml
piergm Dec 21, 2023
4fc26d6
Merge branch 'main' into field_caps-has_value
piergm Dec 21, 2023
f886135
closing readers
piergm Dec 22, 2023
47af25e
small refactor
piergm Dec 22, 2023
683459f
move listener afterIndexShardStarted and close reader
piergm Dec 22, 2023
597f5a8
closes just searcher
piergm Dec 27, 2023
752d31e
updated tests
piergm Dec 28, 2023
a21d6fa
spotless
piergm Dec 28, 2023
c15dd78
added 1 to number of refresh of FrozenIndex test
piergm Dec 28, 2023
d6e9e65
updated docs and added tests
piergm Dec 28, 2023
8d1fa0a
Merge branch 'main' into field_caps-has_value
piergm Dec 28, 2023
58ab703
iter
piergm Dec 28, 2023
c19d573
iter
piergm Dec 28, 2023
76e5060
moved afterShardCreated to afterIndexCreated
piergm Dec 28, 2023
d53c56d
CI
piergm Dec 28, 2023
60d3731
Merge branch 'elastic:main' into field_caps-has_value
piergm Dec 29, 2023
322d65d
iter
piergm Dec 29, 2023
6e90f58
updated bwc tests
piergm Dec 29, 2023
5a91222
spotless
piergm Dec 29, 2023
4f4ef65
spotless
piergm Jan 3, 2024
8f37313
Merge branch 'main' into field_caps-has_value
piergm Jan 3, 2024
5add85b
alias are now returned if original field has value
piergm Jan 3, 2024
c1abe2b
mock IndexShard in tests
piergm Jan 3, 2024
d2332bd
reworked field caps request and response tests
piergm Jan 3, 2024
a500abd
removed todo
piergm Jan 3, 2024
62759c6
spotless
piergm Jan 3, 2024
39821d9
first set of tests
piergm Jan 3, 2024
7999611
set instead of map
piergm Jan 8, 2024
9c97ee5
iter
piergm Jan 10, 2024
734a72a
iter
piergm Jan 10, 2024
6da0369
iter
piergm Jan 10, 2024
ca6893a
more iter
piergm Jan 10, 2024
b6a7dee
Merge branch 'main' into field_caps-has_value
piergm Jan 10, 2024
db70f45
spotless
piergm Jan 10, 2024
e6c528d
updated tests
piergm Jan 10, 2024
cb2669b
iter
piergm Jan 15, 2024
a16e6ba
Merge branch 'main' into field_caps-has_value
piergm Jan 15, 2024
cc4edd3
correct tests resolve wrong merge of indexResponse
piergm Jan 15, 2024
75bea68
updated docs
piergm Jan 15, 2024
10ec350
add tests for nested fields
piergm Jan 15, 2024
8f848b8
add tests for obj fields
piergm Jan 15, 2024
774a21a
Merge branch 'elastic:main' into field_caps-has_value
piergm Jan 15, 2024
fc4c198
iter + tests
piergm Jan 16, 2024
b860d7e
Merge branch 'main' into field_caps-has_value
piergm Jan 16, 2024
7d17ca3
propagate flag in remote requests + comments
piergm Jan 16, 2024
9713967
Merge branch 'main' into field_caps-has_value
piergm Jan 18, 2024
c22377d
Update docs/reference/search/field-caps.asciidoc
piergm Jan 23, 2024
fdfbe3b
Merge branch 'main' into field_caps-has_value
piergm Jan 24, 2024
18e987b
iter
piergm Jan 24, 2024
31706c2
for loops instead of lambdas
piergm Jan 24, 2024
09bc9a6
Map instead of Set to leverage putIfAbsent for perf
piergm Jan 24, 2024
ed68390
Merge branch 'main' into field_caps-has_value
piergm Jan 25, 2024
49c6d87
handle updated docs
piergm Jan 25, 2024
341cd7c
lazy load non-empty fields
piergm Jan 25, 2024
8f8e623
revert frozenIndexTests due to lazy loading
piergm Jan 25, 2024
9d1fd93
sys prop to diable field has value feature
piergm Jan 30, 2024
25c906f
if feature disable use legacy hash
piergm Jan 30, 2024
7c768c8
moved field-has-value logic after refresh
piergm Jan 31, 2024
da717bd
Merge branch 'main' into field_caps-has_value
piergm Jan 31, 2024
578677d
Merge branch 'elastic:main' into field_caps-has_value
piergm Jan 31, 2024
72c972e
Update docs/reference/search/field-caps.asciidoc
piergm Jan 31, 2024
001e696
Merge branch 'main' into field_caps-has_value
piergm Feb 1, 2024
513083e
test for feature_rank
piergm Jan 31, 2024
da846a6
index shard to update field has value after refresh
piergm Feb 1, 2024
9547296
removed rank_feature tests from 'server'
piergm Feb 1, 2024
653a8f0
rank_feature tests in 'extras'
piergm Feb 1, 2024
50d8a8f
move fieldHasValue in MappedFieldType
piergm Feb 5, 2024
0cc060c
Merge branch 'main' into field_caps-has_value
piergm Feb 5, 2024
8fa1d0d
iter
piergm Feb 5, 2024
06b1641
iter
piergm Feb 5, 2024
f30daaf
javadoc + cleaning
piergm Feb 5, 2024
f290e71
fixing after restart issues
piergm Feb 5, 2024
e1f86ea
Merge branch 'main' into field_caps-has_value
piergm Feb 5, 2024
3ab7529
renamed include_fields_with_no_value to include_empty_fields + iter
piergm Feb 5, 2024
a5c269d
Merge branch 'elastic:main' into field_caps-has_value
piergm Feb 6, 2024
18c4353
keeping list of FieldInfos instead of merging them since they are alr…
piergm Feb 6, 2024
5a75987
rank_feature fieldType unitTests
piergm Feb 6, 2024
1bf7d39
RankFeatureMetaFieldType unitTests
piergm Feb 6, 2024
f5ae465
ScriptFieldTypes unitTests
piergm Feb 6, 2024
54edf53
ConstantFieldTypes unitTests
piergm Feb 6, 2024
09e6d92
MappedFieldTypeTest
piergm Feb 6, 2024
2f0ac60
iter
piergm Feb 6, 2024
8fa1b6e
renamed tests
piergm Feb 6, 2024
fa390e4
merging fieldInfos
piergm Feb 6, 2024
6f3ed17
rename
piergm Feb 6, 2024
1339277
removed tests covered by unit tests
piergm Feb 6, 2024
44904c8
basic yaml test
piergm Feb 6, 2024
29b93cd
iter
piergm Feb 6, 2024
8156756
corrected tests
piergm Feb 6, 2024
a7b64c4
yaml tests
piergm Feb 6, 2024
fe5245d
Merge branch 'main' into field_caps-has_value
piergm Feb 6, 2024
7391105
Merge branch 'elastic:main' into field_caps-has_value
piergm Feb 6, 2024
482c11f
yaml tests
piergm Feb 6, 2024
d9cd4f8
I've found restSpec
piergm Feb 6, 2024
9498dce
Merge branch 'main' into field_caps-has_value
piergm Feb 6, 2024
57de9f7
test
piergm Feb 6, 2024
8868e4d
iter
piergm Feb 7, 2024
f2db1cd
tests
piergm Feb 7, 2024
c0f453d
renamed test to standard
piergm Feb 7, 2024
5cef376
moved unmapped field to yml test
piergm Feb 7, 2024
daf034f
multi-cluster test
piergm Feb 7, 2024
5c8ea58
tested default behaviour in FieldTypeTestCase and overwritten when ne…
piergm Feb 7, 2024
4555a0e
removed old test class
piergm Feb 7, 2024
be7483f
standardizing tests
piergm Feb 7, 2024
058c6c8
Merge branch 'elastic:main' into field_caps-has_value
piergm Feb 7, 2024
b1aeb86
multi cluster tests
piergm Feb 7, 2024
4789d78
maybe fixed multi-cluster
piergm Feb 7, 2024
2e8f7b7
Merge branch 'main' into field_caps-has_value
piergm Feb 7, 2024
8115dcd
iter
piergm Feb 7, 2024
a4e2d8d
removed empty spaces
piergm Feb 8, 2024
b6d667b
Merge branch 'elastic:main' into field_caps-has_value
piergm Feb 8, 2024
4a2d503
iter
piergm Feb 8, 2024
be4e91a
Merge branch 'main' into field_caps-has_value
piergm Feb 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/changelog/103651.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 103651
summary: Flag in `_field_caps` to return only fields with values in index
area: Search
type: enhancement
issues: []
4 changes: 4 additions & 0 deletions docs/reference/search/field-caps.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ include::{es-repo-dir}/rest-api/common-parms.asciidoc[tag=index-ignore-unavailab
(Optional, Boolean) If `true`, unmapped fields that are mapped in one index but not in another are included in the response. Fields that don't have any mapping are never included.
Defaults to `false`.

`include_empty_fields`::
(Optional, Boolean) If `false`, fields that never had a value in any shards are not included in the response. Fields that are not empty are always included. This flag does not consider deletions and updates. If a field was non-empty and all the documents containing that field were deleted or the field was removed by updates, it will still be returned even if the flag is `false`.
Defaults to `true`.

`filters`::
(Optional, string) Comma-separated list of filters to apply to the response.
+
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
package org.elasticsearch.index.mapper.extras;

import org.apache.lucene.document.FeatureField;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.FieldInfos;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
Expand Down Expand Up @@ -38,6 +40,7 @@
*/
public class RankFeatureFieldMapper extends FieldMapper {

public static final String NAME = "_feature";
public static final String CONTENT_TYPE = "rank_feature";

private static RankFeatureFieldType ft(FieldMapper in) {
Expand Down Expand Up @@ -128,7 +131,17 @@ public boolean positiveScoreImpact() {

@Override
public Query existsQuery(SearchExecutionContext context) {
return new TermQuery(new Term("_feature", name()));
return new TermQuery(new Term(NAME, name()));
}

@Override
public boolean fieldHasValue(FieldInfos fieldInfos) {
for (FieldInfo fieldInfo : fieldInfos) {
if (fieldInfo.getName().equals(NAME)) {
return true;
}
}
return false;
}

@Override
Expand Down Expand Up @@ -208,7 +221,7 @@ protected void parseCreateField(DocumentParserContext context) throws IOExceptio
value = 1 / value;
}

context.doc().addWithKey(name(), new FeatureField("_feature", name(), value));
context.doc().addWithKey(name(), new FeatureField(NAME, name(), value));
}

private static Float objectToFloat(Object value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

package org.elasticsearch.index.mapper.extras;

import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.FieldInfos;
import org.apache.lucene.search.Query;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.MetadataFieldMapper;
Expand Down Expand Up @@ -35,7 +37,8 @@ public static final class RankFeatureMetaFieldType extends MappedFieldType {

public static final RankFeatureMetaFieldType INSTANCE = new RankFeatureMetaFieldType();

private RankFeatureMetaFieldType() {
// made visible for tests
RankFeatureMetaFieldType() {
super(NAME, false, false, false, TextSearchInfo.NONE, Collections.emptyMap());
}

Expand All @@ -54,6 +57,16 @@ public Query existsQuery(SearchExecutionContext context) {
throw new UnsupportedOperationException("Cannot run exists query on [_feature]");
}

@Override
public boolean fieldHasValue(FieldInfos fieldInfos) {
for (FieldInfo fieldInfo : fieldInfos) {
if (fieldInfo.getName().equals(NAME)) {
return true;
}
}
return false;
}

@Override
public Query termQuery(Object value, SearchExecutionContext context) {
throw new UnsupportedOperationException("The [_feature] field may not be queried directly");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.index.mapper.extras;

import org.elasticsearch.action.fieldcaps.FieldCapabilities;
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse;
import org.elasticsearch.action.support.ActiveShardCount;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.ESIntegTestCase;
import org.hamcrest.Matchers;
import org.junit.Before;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;

import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;

@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST)
public class FieldCapsRankFeatureTests extends ESIntegTestCase {
private final String INDEX = "index-1";

@Override
protected Collection<Class<? extends Plugin>> nodePlugins() {
var plugins = new ArrayList<>(super.nodePlugins());
plugins.add(MapperExtrasPlugin.class);
return plugins;
}

@Before
public void setUpIndices() {
assertAcked(
prepareCreate(INDEX).setWaitForActiveShards(ActiveShardCount.ALL)
.setSettings(indexSettings())
.setMapping("fooRank", "type=rank_feature", "barRank", "type=rank_feature")
);
}

public void testRankFeatureInIndex() {
FieldCapabilitiesResponse response = client().prepareFieldCaps(INDEX).setFields("*").setincludeEmptyFields(false).get();
assertFalse(response.get().containsKey("fooRank"));
assertFalse(response.get().containsKey("barRank"));
prepareIndex(INDEX).setSource("fooRank", 8).setSource("barRank", 8).get();
refresh(INDEX);

response = client().prepareFieldCaps(INDEX).setFields("*").setincludeEmptyFields(false).get();
assertEquals(1, response.getIndices().length);
assertEquals(response.getIndices()[0], INDEX);
assertThat(response.get(), Matchers.hasKey("fooRank"));
// Check the capabilities for the 'fooRank' field.
Map<String, FieldCapabilities> fooRankField = response.getField("fooRank");
assertEquals(1, fooRankField.size());
assertThat(fooRankField, Matchers.hasKey("rank_feature"));
assertEquals(
new FieldCapabilities("fooRank", "rank_feature", false, true, false, null, null, null, Collections.emptyMap()),
fooRankField.get("rank_feature")
);
}

public void testRankFeatureInIndexAfterRestart() throws Exception {
prepareIndex(INDEX).setSource("fooRank", 8).get();
internalCluster().fullRestart();
ensureGreen(INDEX);

FieldCapabilitiesResponse response = client().prepareFieldCaps(INDEX).setFields("*").setincludeEmptyFields(false).get();

assertEquals(1, response.getIndices().length);
assertEquals(response.getIndices()[0], INDEX);
assertThat(response.get(), Matchers.hasKey("fooRank"));
// Check the capabilities for the 'fooRank' field.
Map<String, FieldCapabilities> fooRankField = response.getField("fooRank");
assertEquals(1, fooRankField.size());
assertThat(fooRankField, Matchers.hasKey("rank_feature"));
assertEquals(
new FieldCapabilities("fooRank", "rank_feature", false, true, false, null, null, null, Collections.emptyMap()),
fooRankField.get("rank_feature")
);
}

public void testAllRankFeatureReturnedIfOneIsPresent() {
prepareIndex(INDEX).setSource("fooRank", 8).get();
refresh(INDEX);

FieldCapabilitiesResponse response = client().prepareFieldCaps(INDEX).setFields("*").setincludeEmptyFields(false).get();

assertEquals(1, response.getIndices().length);
assertEquals(response.getIndices()[0], INDEX);
assertThat(response.get(), Matchers.hasKey("fooRank"));
// Check the capabilities for the 'fooRank' field.
Map<String, FieldCapabilities> fooRankField = response.getField("fooRank");
assertEquals(1, fooRankField.size());
assertThat(fooRankField, Matchers.hasKey("rank_feature"));
assertEquals(
new FieldCapabilities("fooRank", "rank_feature", false, true, false, null, null, null, Collections.emptyMap()),
fooRankField.get("rank_feature")
);
assertThat(response.get(), Matchers.hasKey("barRank"));
// Check the capabilities for the 'barRank' field.
Map<String, FieldCapabilities> barRankField = response.getField("barRank");
assertEquals(1, barRankField.size());
assertThat(barRankField, Matchers.hasKey("rank_feature"));
assertEquals(
new FieldCapabilities("barRank", "rank_feature", false, true, false, null, null, null, Collections.emptyMap()),
barRankField.get("rank_feature")
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

package org.elasticsearch.index.mapper.extras;

import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.FieldInfos;
import org.elasticsearch.index.mapper.FieldTypeTestCase;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.MapperBuilderContext;
Expand All @@ -19,7 +21,7 @@
public class RankFeatureFieldTypeTests extends FieldTypeTestCase {

public void testIsNotAggregatable() {
MappedFieldType fieldType = new RankFeatureFieldMapper.RankFeatureFieldType("field", Collections.emptyMap(), true, null);
MappedFieldType fieldType = getMappedFieldType();
assertFalse(fieldType.isAggregatable());
}

Expand All @@ -32,4 +34,28 @@ public void testFetchSourceValue() throws IOException {
assertEquals(List.of(42.9f), fetchSourceValue(mapper, "42.9"));
assertEquals(List.of(2.0f), fetchSourceValue(mapper, null));
}

@Override
public void testFieldHasValue() {
MappedFieldType fieldType = getMappedFieldType();
FieldInfos fieldInfos = new FieldInfos(new FieldInfo[] { getFieldInfoWithName("_feature") });
assertTrue(fieldType.fieldHasValue(fieldInfos));
}

@Override
public void testFieldHasValueWithEmptyFieldInfos() {
MappedFieldType fieldType = getMappedFieldType();
assertFalse(fieldType.fieldHasValue(FieldInfos.EMPTY));
}

public void testFieldEmptyIfNameIsPresentInFieldInfos() {
MappedFieldType fieldType = getMappedFieldType();
FieldInfos fieldInfos = new FieldInfos(new FieldInfo[] { getFieldInfoWithName("field") });
assertFalse(fieldType.fieldHasValue(fieldInfos));
}

@Override
public MappedFieldType getMappedFieldType() {
return new RankFeatureFieldMapper.RankFeatureFieldType("field", Collections.emptyMap(), true, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@

package org.elasticsearch.index.mapper.extras;

import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.FieldInfos;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.compress.CompressedXContent;
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.DocumentParsingException;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.MapperServiceTestCase;
import org.elasticsearch.index.mapper.Mapping;
Expand Down Expand Up @@ -73,4 +76,19 @@ public void testDocumentParsingFailsOnMetaField() throws Exception {
CoreMatchers.containsString("Field [" + rfMetaField + "] is a metadata field and cannot be added inside a document.")
);
}

@Override
public void testFieldHasValue() {
assertTrue(getMappedFieldType().fieldHasValue(new FieldInfos(new FieldInfo[] { getFieldInfoWithName("_feature") })));
}

@Override
public void testFieldHasValueWithEmptyFieldInfos() {
assertFalse(getMappedFieldType().fieldHasValue(FieldInfos.EMPTY));
}

@Override
public MappedFieldType getMappedFieldType() {
return new RankFeatureMetaFieldMapper.RankFeatureMetaFieldType();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
field_caps:
index: 'my_remote_cluster:some_index_that_doesnt_exist'
fields: [number]

- match: { error.type: "index_not_found_exception" }
- match: { error.reason: "no such index [some_index_that_doesnt_exist]" }

Expand Down Expand Up @@ -156,3 +155,38 @@
- length: {fields.number: 1}
- match: {fields.number.long.searchable: true}
- match: {fields.number.long.aggregatable: true}

---
"Field caps with with include_empty_fields false":
- skip:
version: " - 8.12.99"
reason: include_empty_fields has been added in 8.13.0
- do:
indices.create:
index: field_caps_index_5
body:
mappings:
properties:
number:
type: double
empty-baz:
type: text

- do:
index:
index: field_caps_index_5
body: { number: "42", unmapped-bar: "bar" }

- do:
indices.refresh:
index: [field_caps_index_5]
- do:
field_caps:
include_empty_fields: false
index: 'field_caps_index_5,my_remote_cluster:field_*'
fields: '*'

- is_true: fields.number
- is_false: fields.empty-baz
- is_true: fields.unmapped-bar
- is_true: fields._index
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@
nested2:
type: keyword
doc_values: false
- do:
index:
index: field_caps_index_1
body: { number: "42", unmapped-bar: "bar" }
- do:
indices.create:
index: test_index
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@
"types": {
"type": "list",
"description":"Only return results for fields that have one of the types in the list"
},
"include_empty_fields": {
"type":"boolean",
"default": true,
"description":"Include empty fields in result"
}
},
"body":{
Expand Down
Loading