Filtering maps had false hit is a field was a prefix (but not a match) of an include. Also, exact matching a key whose value is an object resulted in an empty value.

Closes #3288
This commit is contained in:
Boaz Leskes 2013-07-04 10:22:45 +02:00
parent 4c8f3de34b
commit 018ca58cdb
2 changed files with 145 additions and 7 deletions

View File

@ -141,6 +141,10 @@ public class XContentMapValues {
} }
private static void filter(Map<String, Object> map, Map<String, Object> into, String[] includes, String[] excludes, StringBuilder sb) { private static void filter(Map<String, Object> map, Map<String, Object> into, String[] includes, String[] excludes, StringBuilder sb) {
if (includes.length == 0 && excludes.length == 0) {
into.putAll(map);
return;
}
for (Map.Entry<String, Object> entry : map.entrySet()) { for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
int mark = sb.length(); int mark = sb.length();
@ -160,12 +164,24 @@ public class XContentMapValues {
sb.setLength(mark); sb.setLength(mark);
continue; continue;
} }
boolean exactIncludeMatch = false;
if (includes.length > 0) { if (includes.length > 0) {
boolean atLeastOnOneIncludeMatched = false; boolean atLeastOnOneIncludeMatched = false;
for (String include : includes) { for (String include : includes) {
// check for prefix as well, something like: obj1.arr1.* // check for prefix as well to see if we need to zero in, something like: obj1.arr1.*
// note, this does not work well with middle matches, like obj1.*.obj3 // note, this does not work well with middle matches, like obj1.*.obj3
if (include.startsWith(path) || Regex.simpleMatch(include, path)) { if (include.startsWith(path)) {
if (include.length() == path.length()) {
atLeastOnOneIncludeMatched = true;
exactIncludeMatch = true;
break;
} else if (include.length() > path.length() && include.charAt(path.length()) == '.') {
// include might may match deeper paths. Dive deeper.
atLeastOnOneIncludeMatched = true;
break;
}
}
if (Regex.simpleMatch(include, path)) {
atLeastOnOneIncludeMatched = true; atLeastOnOneIncludeMatched = true;
break; break;
} }
@ -179,14 +195,16 @@ public class XContentMapValues {
if (entry.getValue() instanceof Map) { if (entry.getValue() instanceof Map) {
Map<String, Object> innerInto = Maps.newHashMap(); Map<String, Object> innerInto = Maps.newHashMap();
filter((Map<String, Object>) entry.getValue(), innerInto, includes, excludes, sb); // if we had an exact match, we want give deeper excludes their chance
filter((Map<String, Object>) entry.getValue(), innerInto, exactIncludeMatch ? Strings.EMPTY_ARRAY : includes, excludes, sb);
if (!innerInto.isEmpty()) { if (!innerInto.isEmpty()) {
into.put(entry.getKey(), innerInto); into.put(entry.getKey(), innerInto);
} }
} else if (entry.getValue() instanceof List) { } else if (entry.getValue() instanceof List) {
List<Object> list = (List<Object>) entry.getValue(); List<Object> list = (List<Object>) entry.getValue();
List<Object> innerInto = new ArrayList<Object>(list.size()); List<Object> innerInto = new ArrayList<Object>(list.size());
filter(list, innerInto, includes, excludes, sb); // if we had an exact match, we want give deeper excludes their chance
filter(list, innerInto, exactIncludeMatch ? Strings.EMPTY_ARRAY : includes, excludes, sb);
into.put(entry.getKey(), innerInto); into.put(entry.getKey(), innerInto);
} else { } else {
into.put(entry.getKey(), entry.getValue()); into.put(entry.getKey(), entry.getValue());
@ -196,6 +214,11 @@ public class XContentMapValues {
} }
private static void filter(List<Object> from, List<Object> to, String[] includes, String[] excludes, StringBuilder sb) { private static void filter(List<Object> from, List<Object> to, String[] includes, String[] excludes, StringBuilder sb) {
if (includes.length == 0 && excludes.length == 0) {
to.addAll(from);
return;
}
for (Object o : from) { for (Object o : from) {
if (o instanceof Map) { if (o instanceof Map) {
Map<String, Object> innerInto = Maps.newHashMap(); Map<String, Object> innerInto = Maps.newHashMap();

View File

@ -28,11 +28,14 @@ import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.support.XContentMapValues; import org.elasticsearch.common.xcontent.support.XContentMapValues;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.hamcrest.core.IsEqual.equalTo;
/** /**
*/ */
@ -73,7 +76,7 @@ public class XContentMapValuesTests {
source = XContentFactory.xContent(XContentType.JSON).createParser(builder.string()).mapAndClose(); source = XContentFactory.xContent(XContentType.JSON).createParser(builder.string()).mapAndClose();
filter = XContentMapValues.filter(source, new String[]{"path1"}, Strings.EMPTY_ARRAY); filter = XContentMapValues.filter(source, new String[]{"path1"}, Strings.EMPTY_ARRAY);
assertThat(filter.size(), equalTo(0)); assertThat(filter.size(), equalTo(1));
filter = XContentMapValues.filter(source, new String[]{"path1*"}, Strings.EMPTY_ARRAY); filter = XContentMapValues.filter(source, new String[]{"path1*"}, Strings.EMPTY_ARRAY);
assertThat(filter.get("path1"), equalTo(source.get("path1"))); assertThat(filter.get("path1"), equalTo(source.get("path1")));
@ -202,13 +205,125 @@ public class XContentMapValuesTests {
public void testThatFilteringWithNestedArrayAndExclusionWorks() throws Exception { public void testThatFilteringWithNestedArrayAndExclusionWorks() throws Exception {
XContentBuilder builder = XContentFactory.jsonBuilder().startObject() XContentBuilder builder = XContentFactory.jsonBuilder().startObject()
.startArray("coordinates") .startArray("coordinates")
.startArray().value("foo").endArray() .startArray().value("foo").endArray()
.endArray() .endArray()
.endObject(); .endObject();
Tuple<XContentType, Map<String, Object>> mapTuple = XContentHelper.convertToMap(builder.bytes(), true); Tuple<XContentType, Map<String, Object>> mapTuple = XContentHelper.convertToMap(builder.bytes(), true);
Map<String, Object> filteredSource = XContentMapValues.filter(mapTuple.v2(), Strings.EMPTY_ARRAY, new String[]{"nonExistingField"}); Map<String, Object> filteredSource = XContentMapValues.filter(mapTuple.v2(), Strings.EMPTY_ARRAY, new String[]{"nonExistingField"});
assertThat(mapTuple.v2(), equalTo(filteredSource)); assertThat(mapTuple.v2(), equalTo(filteredSource));
} }
@Test
public void prefixedNamesFilteringTest() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("obj", "value");
map.put("obj_name", "value_name");
Map<String, Object> filterdMap = XContentMapValues.filter(map, new String[]{"obj_name"}, Strings.EMPTY_ARRAY);
assertThat(filterdMap.size(), equalTo(1));
assertThat((String) filterdMap.get("obj_name"), equalTo("value_name"));
}
@Test
@SuppressWarnings("unchecked")
public void nestedFilteringTest() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("field", "value");
map.put("array",
Arrays.asList(
1,
new HashMap<String, Object>() {{
put("nested", 2);
put("nested_2", 3);
}}));
Map<String, Object> falteredMap = XContentMapValues.filter(map, new String[]{"array.nested"}, Strings.EMPTY_ARRAY);
assertThat(falteredMap.size(), equalTo(1));
// Selecting members of objects within arrays (ex. [ 1, { nested: "value"} ]) always returns all values in the array (1 in the ex)
// this is expected behavior as this types of objects are not supported in ES
assertThat((Integer) ((List) falteredMap.get("array")).get(0), equalTo(1));
assertThat(((Map<String, Object>) ((List) falteredMap.get("array")).get(1)).size(), equalTo(1));
assertThat((Integer) ((Map<String, Object>) ((List) falteredMap.get("array")).get(1)).get("nested"), equalTo(2));
falteredMap = XContentMapValues.filter(map, new String[]{"array.*"}, Strings.EMPTY_ARRAY);
assertThat(falteredMap.size(), equalTo(1));
assertThat((Integer) ((List) falteredMap.get("array")).get(0), equalTo(1));
assertThat(((Map<String, Object>) ((List) falteredMap.get("array")).get(1)).size(), equalTo(2));
map.clear();
map.put("field", "value");
map.put("obj",
new HashMap<String, Object>() {{
put("field", "value");
put("field2", "value2");
}});
falteredMap = XContentMapValues.filter(map, new String[]{"obj.field"}, Strings.EMPTY_ARRAY);
assertThat(falteredMap.size(), equalTo(1));
assertThat(((Map<String, Object>) falteredMap.get("obj")).size(), equalTo(1));
assertThat((String) ((Map<String, Object>) falteredMap.get("obj")).get("field"), equalTo("value"));
falteredMap = XContentMapValues.filter(map, new String[]{"obj.*"}, Strings.EMPTY_ARRAY);
assertThat(falteredMap.size(), equalTo(1));
assertThat(((Map<String, Object>) falteredMap.get("obj")).size(), equalTo(2));
assertThat((String) ((Map<String, Object>) falteredMap.get("obj")).get("field"), equalTo("value"));
assertThat((String) ((Map<String, Object>) falteredMap.get("obj")).get("field2"), equalTo("value2"));
}
@SuppressWarnings("unchecked")
@Test
public void completeObjectFilteringTest() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("field", "value");
map.put("obj",
new HashMap<String, Object>() {{
put("field", "value");
put("field2", "value2");
}});
map.put("array",
Arrays.asList(
1,
new HashMap<String, Object>() {{
put("field", "value");
put("field2", "value2");
}}));
Map<String, Object> filteredMap = XContentMapValues.filter(map, new String[]{"obj"}, Strings.EMPTY_ARRAY);
assertThat(filteredMap.size(), equalTo(1));
assertThat(((Map<String, Object>) filteredMap.get("obj")).size(), equalTo(2));
assertThat(((Map<String, Object>) filteredMap.get("obj")).get("field").toString(), equalTo("value"));
assertThat(((Map<String, Object>) filteredMap.get("obj")).get("field2").toString(), equalTo("value2"));
filteredMap = XContentMapValues.filter(map, new String[]{"obj"}, new String[]{"*.field2"});
assertThat(filteredMap.size(), equalTo(1));
assertThat(((Map<String, Object>) filteredMap.get("obj")).size(), equalTo(1));
assertThat(((Map<String, Object>) filteredMap.get("obj")).get("field").toString(), equalTo("value"));
filteredMap = XContentMapValues.filter(map, new String[]{"array"}, new String[]{});
assertThat(filteredMap.size(), equalTo(1));
assertThat(((List) filteredMap.get("array")).size(), equalTo(2));
assertThat((Integer) ((List) filteredMap.get("array")).get(0), equalTo(1));
assertThat(((Map<String, Object>) ((List) filteredMap.get("array")).get(1)).size(), equalTo(2));
filteredMap = XContentMapValues.filter(map, new String[]{"array"}, new String[]{"*.field2"});
assertThat(filteredMap.size(), equalTo(1));
assertThat(((List) filteredMap.get("array")).size(), equalTo(2));
assertThat((Integer) ((List) filteredMap.get("array")).get(0), equalTo(1));
assertThat(((Map<String, Object>) ((List) filteredMap.get("array")).get(1)).size(), equalTo(1));
assertThat(((Map<String, Object>) ((List) filteredMap.get("array")).get(1)).get("field").toString(), equalTo("value"));
}
@Test
public void filterWithEmptyIncludesExcludes() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("field", "value");
Map<String, Object> filteredMap = XContentMapValues.filter(map, Strings.EMPTY_ARRAY, Strings.EMPTY_ARRAY);
assertThat(filteredMap.size(), equalTo(1));
assertThat(filteredMap.get("field").toString(), equalTo("value"));
}
} }