Skip to content
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

Do not fail on duplicated content field filters #85382

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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/85382.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 85382
summary: Do not fail on duplicated content field filters
area: Mapping
type: bug
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import java.util.stream.IntStream;

Expand Down Expand Up @@ -324,8 +325,9 @@ public void testDotsAndDoubleWildcardInExcludedFieldName() throws IOException {
}

@Override
protected final void testFilter(Builder expected, Builder sample, Set<String> includes, Set<String> excludes) throws IOException {
testFilter(expected, sample, includes, excludes, false);
protected final void testFilter(Builder expected, Builder sample, Collection<String> includes, Collection<String> excludes)
throws IOException {
testFilter(expected, sample, Set.copyOf(includes), Set.copyOf(excludes), false);
}

private void testFilter(Builder expected, Builder sample, Set<String> includes, Set<String> excludes, boolean matchFieldNamesWithDots)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ static XContentFieldFilter newFieldFilter(String[] includes, String[] excludes)
};
} else {
final XContentParserConfiguration parserConfig = XContentParserConfiguration.EMPTY.withFiltering(
Set.of(includes),
Set.of(excludes),
Set.copyOf(Arrays.asList(includes)),
Set.copyOf(Arrays.asList(excludes)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a chance to review it only after the pr was merged, may be we could consider this for a followup or a future change:

May be we should have a dedicated method for this in CollectionUtils so that intention is clear and nobody revert/simplify code to the shorter Set.of version. May be something like CollectionUtils.setOfCollectionWithPossibleDuplicates(Collection<T> c)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not advocating against your suggestion, but we do have tests for this now so reverting the change would cause test failures

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduced a test for duplicate filters. I think that's what you want, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, the test would catch the revert. I was suggesting to have a dedicated set factory that would display the intention (create set from collection that may contain duplicates)

true
);
return (originalSource, contentType) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
import org.elasticsearch.xcontent.XContentType;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

Expand All @@ -36,7 +38,8 @@

public class XContentFieldFilterTests extends AbstractFilteringTestCase {
@Override
protected void testFilter(Builder expected, Builder actual, Set<String> includes, Set<String> excludes) throws IOException {
protected void testFilter(Builder expected, Builder actual, Collection<String> includes, Collection<String> excludes)
throws IOException {
final XContentType xContentType = randomFrom(XContentType.values());
final boolean humanReadable = randomBoolean();
String[] sourceIncludes;
Expand All @@ -56,7 +59,8 @@ protected void testFilter(Builder expected, Builder actual, Set<String> includes
assertMap(XContentHelper.convertToMap(ref, true, xContentType).v2(), matchesMap(toMap(expected, xContentType, humanReadable)));
}

private void testFilter(String expectedJson, String actualJson, Set<String> includes, Set<String> excludes) throws IOException {
private void testFilter(String expectedJson, String actualJson, Collection<String> includes, Collection<String> excludes)
throws IOException {
CheckedFunction<String, Builder, IOException> toBuilder = json -> {
XContentParser parser = XContentHelper.createParser(XContentParserConfiguration.EMPTY, new BytesArray(json), XContentType.JSON);
if ((parser.currentToken() == null) && (parser.nextToken() == null)) {
Expand All @@ -82,6 +86,36 @@ public void testPrefixedNamesFilteringTest() throws IOException {
testFilter(expected, actual, singleton("obj_name"), emptySet());
}

public void testDuplicatedIncludes() throws IOException {
String actual = """
{
"obj": "value",
"obj_name": "value_name"
}
""";
String expected = """
{
"obj_name": "value_name"
}
""";
testFilter(expected, actual, List.of("obj_name", "obj_name"), emptySet());
}

public void testDuplicatedExcludes() throws IOException {
String actual = """
{
"obj": "value",
"obj_name": "value_name"
}
""";
String expected = """
{
"obj": "value"
}
""";
testFilter(expected, actual, emptySet(), List.of("obj_name", "obj_name"));
}

public void testNestedFiltering() throws IOException {
String actual = """
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.elasticsearch.common.xcontent.XContentHelper.convertToMap;
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
Expand All @@ -44,7 +44,8 @@
public class XContentMapValuesTests extends AbstractFilteringTestCase {

@Override
protected void testFilter(Builder expected, Builder actual, Set<String> includes, Set<String> excludes) throws IOException {
protected void testFilter(Builder expected, Builder actual, Collection<String> includes, Collection<String> excludes)
throws IOException {
final XContentType xContentType = randomFrom(XContentType.values());
final boolean humanReadable = randomBoolean();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,22 @@ public void testIncludes() throws Exception {
assertThat(sourceAsMap.containsKey("path2"), equalTo(false));
}

public void testDuplicatedIncludes() throws Exception {
DocumentMapper documentMapper = createDocumentMapper(
topMapping(b -> b.startObject("_source").array("includes", "path1", "path1").endObject())
);

ParsedDocument doc = documentMapper.parse(source(b -> {
b.startObject("path1").field("field1", "value1").endObject();
b.startObject("path2").field("field2", "value2").endObject();
}));

IndexableField sourceField = doc.rootDoc().getField("_source");
try (XContentParser parser = createParser(JsonXContent.jsonXContent, new BytesArray(sourceField.binaryValue()))) {
assertEquals(Map.of("path1", Map.of("field1", "value1")), parser.map());
}
}

public void testExcludes() throws Exception {
DocumentMapper documentMapper = createDocumentMapper(
topMapping(b -> b.startObject("_source").array("excludes", "path1*").endObject())
Expand All @@ -108,6 +124,22 @@ public void testExcludes() throws Exception {
assertThat(sourceAsMap.containsKey("path2"), equalTo(true));
}

public void testDuplicatedExcludes() throws Exception {
DocumentMapper documentMapper = createDocumentMapper(
topMapping(b -> b.startObject("_source").array("excludes", "path1", "path1").endObject())
);

ParsedDocument doc = documentMapper.parse(source(b -> {
b.startObject("path1").field("field1", "value1").endObject();
b.startObject("path2").field("field2", "value2").endObject();
}));

IndexableField sourceField = doc.rootDoc().getField("_source");
try (XContentParser parser = createParser(JsonXContent.jsonXContent, new BytesArray(sourceField.binaryValue()))) {
assertEquals(Map.of("path2", Map.of("field2", "value2")), parser.map());
}
}

public void testComplete() throws Exception {

assertTrue(createDocumentMapper(topMapping(b -> {})).sourceMapper().isComplete());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Set;

import static java.util.Collections.emptySet;
Expand All @@ -41,7 +42,8 @@ public abstract class AbstractFilteringTestCase extends ESTestCase {
@FunctionalInterface
protected interface Builder extends CheckedFunction<XContentBuilder, XContentBuilder, IOException> {}

protected abstract void testFilter(Builder expected, Builder actual, Set<String> includes, Set<String> excludes) throws IOException;
protected abstract void testFilter(Builder expected, Builder actual, Collection<String> includes, Collection<String> excludes)
throws IOException;

/** Sample test case */
protected static final Builder SAMPLE = builderFor("sample.json");
Expand Down