SOLR-12362: Uploading docs in JSON now supports child documents as field values

This commit is contained in:
user 2018-06-14 12:14:49 -04:00 committed by David Smiley
parent a26a1bb249
commit 21fe4164de
6 changed files with 354 additions and 169 deletions

View File

@ -74,6 +74,10 @@ New Features
* SOLR-12474: Add an UpdateRequest Object that implements RequestWriter.ContentWriter (noble) * SOLR-12474: Add an UpdateRequest Object that implements RequestWriter.ContentWriter (noble)
* SOLR-12362: Uploading docs in JSON now supports child documents as field values, thus providing a label to the
relationship instead of the current "anonymous" relationship. Use of this experimental feature requires
anonChildDocs=false parameter. (Moshe Bla, David Smiley)
Bug Fixes Bug Fixes
---------------------- ----------------------

View File

@ -93,11 +93,16 @@ public class JsonLoader extends ContentStreamLoader {
protected JSONParser parser; protected JSONParser parser;
protected final int commitWithin; protected final int commitWithin;
protected final boolean overwrite; protected final boolean overwrite;
protected final boolean anonChildDoc;
/**
* {@link CommonParams#ANONYMOUS_CHILD_DOCS} Defaults to true.
*/
public SingleThreadedJsonLoader(SolrQueryRequest req, SolrQueryResponse rsp, UpdateRequestProcessor processor) { public SingleThreadedJsonLoader(SolrQueryRequest req, SolrQueryResponse rsp, UpdateRequestProcessor processor) {
this.processor = processor; this.processor = processor;
this.req = req; this.req = req;
this.rsp = rsp; this.rsp = rsp;
this.anonChildDoc = req.getParams().getBool(CommonParams.ANONYMOUS_CHILD_DOCS, true);
commitWithin = req.getParams().getInt(UpdateParams.COMMIT_WITHIN, -1); commitWithin = req.getParams().getInt(UpdateParams.COMMIT_WITHIN, -1);
overwrite = req.getParams().getBool(UpdateParams.OVERWRITE, true); overwrite = req.getParams().getBool(UpdateParams.OVERWRITE, true);
@ -248,14 +253,28 @@ public class JsonLoader extends ContentStreamLoader {
private SolrInputDocument buildDoc(Map<String, Object> m) { private SolrInputDocument buildDoc(Map<String, Object> m) {
SolrInputDocument result = new SolrInputDocument(); SolrInputDocument result = new SolrInputDocument();
for (Map.Entry<String, Object> e : m.entrySet()) { for (Map.Entry<String, Object> e : m.entrySet()) {
if (e.getKey() == null) {// special case. JsonRecordReader emits child docs with null key if (mapEntryIsChildDoc(e.getValue())) { // parse child documents
if (e.getValue() instanceof List) { if (e.getValue() instanceof List) {
List value = (List) e.getValue(); List value = (List) e.getValue();
for (Object o : value) { for (Object o : value) {
if (o instanceof Map) result.addChildDocument(buildDoc((Map) o)); if (o instanceof Map) {
if (anonChildDoc) {
result.addChildDocument(buildDoc((Map) o));
} else {
// retain the value as a list, even if the list contains a single value.
if(!result.containsKey(e.getKey())) {
result.setField(e.getKey(), new ArrayList<>(1));
}
result.addField(e.getKey(), buildDoc((Map) o));
}
}
} }
} else if (e.getValue() instanceof Map) { } else if (e.getValue() instanceof Map) {
if (anonChildDoc) {
result.addChildDocument(buildDoc((Map) e.getValue())); result.addChildDocument(buildDoc((Map) e.getValue()));
} else {
result.addField(e.getKey(), buildDoc((Map) e.getValue()));
}
} }
} else { } else {
result.setField(e.getKey(), e.getValue()); result.setField(e.getKey(), e.getValue());
@ -530,7 +549,7 @@ public class JsonLoader extends ContentStreamLoader {
} }
String fieldName = parser.getString(); String fieldName = parser.getString();
if (fieldName.equals(JsonLoader.CHILD_DOC_KEY)) { if (anonChildDoc && fieldName.equals(JsonLoader.CHILD_DOC_KEY)) {
ev = parser.nextEvent(); ev = parser.nextEvent();
assertEvent(ev, JSONParser.ARRAY_START); assertEvent(ev, JSONParser.ARRAY_START);
while ((ev = parser.nextEvent()) != JSONParser.ARRAY_END) { while ((ev = parser.nextEvent()) != JSONParser.ARRAY_END) {
@ -554,41 +573,53 @@ public class JsonLoader extends ContentStreamLoader {
private void parseFieldValue(SolrInputField sif) throws IOException { private void parseFieldValue(SolrInputField sif) throws IOException {
int ev = parser.nextEvent(); int ev = parser.nextEvent();
if (ev == JSONParser.OBJECT_START) { if (ev == JSONParser.OBJECT_START) {
parseExtendedFieldValue(sif, ev); parseExtendedFieldValue(ev, sif);
} else { } else {
Object val = parseNormalFieldValue(ev, sif.getName()); Object val = parseNormalFieldValue(ev, sif);
sif.setValue(val); sif.setValue(val);
} }
} }
private void parseExtendedFieldValue(SolrInputField sif, int ev) throws IOException { /**
* A method to either extract an index time boost (deprecated), a map for atomic update, or a child document.
* firstly, a solr document SolrInputDocument constructed. It is then determined whether the document is indeed a childDocument(if it has a unique field).
* If so, it is added.
* Otherwise the document is looped over as a map, and is then parsed as an Atomic Update if that is the case.
* @param ev json parser event
* @param sif input field to add value to.
* @throws IOException in case of parsing exception.
*/
private void parseExtendedFieldValue(int ev, SolrInputField sif) throws IOException {
assert ev == JSONParser.OBJECT_START; assert ev == JSONParser.OBJECT_START;
SolrInputDocument extendedSolrDocument = parseDoc(ev);
if (isChildDoc(extendedSolrDocument)) {
sif.addValue(extendedSolrDocument);
return;
}
Object normalFieldValue = null; Object normalFieldValue = null;
Map<String, Object> extendedInfo = null; Map<String, Object> extendedInfo = null;
for (; ; ) { for (SolrInputField entry: extendedSolrDocument) {
ev = parser.nextEvent(); Object val = entry.getValue();
switch (ev) { String label = entry.getName();
case JSONParser.STRING:
String label = parser.getString();
if ("boost".equals(label)) { if ("boost".equals(label)) {
ev = parser.nextEvent(); Object boostVal = val;
if (ev != JSONParser.NUMBER && if (!(boostVal instanceof Double)) {
ev != JSONParser.LONG &&
ev != JSONParser.BIGNUMBER) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Boost should have number. " throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Boost should have number. "
+ "Unexpected " + JSONParser.getEventString(ev) + " at [" + parser.getPosition() + "], field=" + sif.getName()); + "Unexpected value: " + boostVal.toString() + "field=" + label);
} }
String message = "Ignoring field boost: " + parser.getDouble() + " as index-time boosts are not supported anymore"; String message = "Ignoring field boost: " + boostVal.toString() + " as index-time boosts are not supported anymore";
if (WARNED_ABOUT_INDEX_TIME_BOOSTS.compareAndSet(false, true)) { if (WARNED_ABOUT_INDEX_TIME_BOOSTS.compareAndSet(false, true)) {
log.warn(message); log.warn(message);
} else { } else {
log.debug(message); log.debug(message);
} }
} else if ("value".equals(label)) { } else if ("value".equals(label)) {
normalFieldValue = parseNormalFieldValue(parser.nextEvent(), sif.getName()); normalFieldValue = val;
} else { } else {
// If we encounter other unknown map keys, then use a map // If we encounter other unknown map keys, then use a map
if (extendedInfo == null) { if (extendedInfo == null) {
@ -596,12 +627,8 @@ public class JsonLoader extends ContentStreamLoader {
} }
// for now, the only extended info will be field values // for now, the only extended info will be field values
// we could either store this as an Object or a SolrInputField // we could either store this as an Object or a SolrInputField
Object val = parseNormalFieldValue(parser.nextEvent(), sif.getName());
extendedInfo.put(label, val); extendedInfo.put(label, val);
} }
break;
case JSONParser.OBJECT_END:
if (extendedInfo != null) { if (extendedInfo != null) {
if (normalFieldValue != null) { if (normalFieldValue != null) {
extendedInfo.put("value", normalFieldValue); extendedInfo.put("value", normalFieldValue);
@ -610,28 +637,15 @@ public class JsonLoader extends ContentStreamLoader {
} else { } else {
sif.setValue(normalFieldValue); sif.setValue(normalFieldValue);
} }
return;
default:
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error parsing JSON extended field value. "
+ "Unexpected " + JSONParser.getEventString(ev) + " at [" + parser.getPosition() + "], field=" + sif.getName());
}
}
}
private Object parseNormalFieldValue(int ev, String fieldName) throws IOException {
if (ev == JSONParser.ARRAY_START) {
List<Object> val = parseArrayFieldValue(ev, fieldName);
return val;
} else {
Object val = parseSingleFieldValue(ev, fieldName);
return val;
} }
} }
private Object parseSingleFieldValue(int ev, String fieldName) throws IOException { private Object parseNormalFieldValue(int ev, SolrInputField sif) throws IOException {
return ev == JSONParser.ARRAY_START ? parseArrayFieldValue(ev, sif): parseSingleFieldValue(ev, sif);
}
private Object parseSingleFieldValue(int ev, SolrInputField sif) throws IOException {
switch (ev) { switch (ev) {
case JSONParser.STRING: case JSONParser.STRING:
return parser.getString(); return parser.getString();
@ -647,15 +661,18 @@ public class JsonLoader extends ContentStreamLoader {
parser.getNull(); parser.getNull();
return null; return null;
case JSONParser.ARRAY_START: case JSONParser.ARRAY_START:
return parseArrayFieldValue(ev, fieldName); return parseArrayFieldValue(ev, sif);
case JSONParser.OBJECT_START:
parseExtendedFieldValue(ev, sif);
return sif.getValue();
default: default:
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error parsing JSON field value. " throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error parsing JSON field value. "
+ "Unexpected " + JSONParser.getEventString(ev) + " at [" + parser.getPosition() + "], field=" + fieldName); + "Unexpected " + JSONParser.getEventString(ev) + " at [" + parser.getPosition() + "], field=" + sif.getName());
} }
} }
private List<Object> parseArrayFieldValue(int ev, String fieldName) throws IOException { private List<Object> parseArrayFieldValue(int ev, SolrInputField sif) throws IOException {
assert ev == JSONParser.ARRAY_START; assert ev == JSONParser.ARRAY_START;
ArrayList lst = new ArrayList(2); ArrayList lst = new ArrayList(2);
@ -664,10 +681,24 @@ public class JsonLoader extends ContentStreamLoader {
if (ev == JSONParser.ARRAY_END) { if (ev == JSONParser.ARRAY_END) {
return lst; return lst;
} }
Object val = parseSingleFieldValue(ev, fieldName); Object val = parseSingleFieldValue(ev, sif);
lst.add(val); lst.add(val);
sif.setValue(null);
} }
} }
private boolean isChildDoc(SolrInputDocument extendedFieldValue) {
return extendedFieldValue.containsKey(req.getSchema().getUniqueKeyField().getName());
}
private boolean mapEntryIsChildDoc(Object val) {
if(val instanceof List) {
List listVal = (List) val;
if (listVal.size() == 0) return false;
return listVal.get(0) instanceof Map;
}
return val instanceof Map;
}
} }
private static Object changeChildDoc(Object o) { private static Object changeChildDoc(Object o) {

View File

@ -16,6 +16,11 @@
*/ */
package org.apache.solr.handler; package org.apache.solr.handler;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.UnaryOperator;
import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.SolrInputDocument;
@ -32,10 +37,7 @@ import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.noggit.ObjectBuilder; import org.noggit.ObjectBuilder;
import java.util.Collections; import static org.apache.solr.common.params.CommonParams.ANONYMOUS_CHILD_DOCS;
import java.util.List;
import java.util.Map;
import java.util.function.UnaryOperator;
public class JsonLoaderTest extends SolrTestCaseJ4 { public class JsonLoaderTest extends SolrTestCaseJ4 {
@ -411,6 +413,7 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
" \"id\": \"1.2\",\n" + " \"id\": \"1.2\",\n" +
" \"name\": \"i am the 2nd child\",\n" + " \"name\": \"i am the 2nd child\",\n" +
" \"cat\": \"child\",\n" + " \"cat\": \"child\",\n" +
" \"test_s\": \"test-new-label\",\n" +
" \"grandchildren\": [\n" + " \"grandchildren\": [\n" +
" {\n" + " {\n" +
" \"id\": \"1.2.1\",\n" + " \"id\": \"1.2.1\",\n" +
@ -432,13 +435,23 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
"f", "name:/children/grandchildren/name", "f", "name:/children/grandchildren/name",
"f", "cat:/children/grandchildren/cat"}; "f", "cat:/children/grandchildren/cat"};
@Test
public void testFewParentsAnonymousJsonDoc() throws Exception {
String json = PARENT_TWO_CHILDREN_JSON;
assertFewParents(json, true);
}
@Test
public void testFewParentsJsonDoc() throws Exception { public void testFewParentsJsonDoc() throws Exception {
String json = PARENT_TWO_CHILDREN_JSON; String json = PARENT_TWO_CHILDREN_JSON;
assertFewParents(json, false);
}
private void assertFewParents(String json, boolean anonChildDocsFlag) throws Exception {
SolrQueryRequest req; SolrQueryRequest req;
SolrQueryResponse rsp; SolrQueryResponse rsp;
BufferingRequestProcessor p; BufferingRequestProcessor p;
JsonLoader loader; JsonLoader loader;//multichild test case
{ //multichild test case
final boolean array = random().nextBoolean(); final boolean array = random().nextBoolean();
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
if (array) { if (array) {
@ -452,7 +465,7 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
} }
} }
req = req(PARENT_TWO_CHILDREN_PARAMS); req = req(PARENT_TWO_CHILDREN_PARAMS, ANONYMOUS_CHILD_DOCS, Boolean.toString(anonChildDocsFlag));
req.getContext().put("path", "/update/json/docs"); req.getContext().put("path", "/update/json/docs");
rsp = new SolrQueryResponse(); rsp = new SolrQueryResponse();
p = new BufferingRequestProcessor(null); p = new BufferingRequestProcessor(null);
@ -466,28 +479,42 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
assertOnlyValue("i am the parent", parent, "name"); assertOnlyValue("i am the parent", parent, "name");
assertOnlyValue("parent", parent, "cat"); assertOnlyValue("parent", parent, "cat");
assertEquals(2, parent.getChildDocuments().size()); List<SolrInputDocument> childDocs1;
if(anonChildDocsFlag) {
childDocs1 = parent.getChildDocuments();
} else {
childDocs1 = (List) ((parent.getField("children")).getValue());
}
assertEquals(2, childDocs1.size());
{ {
final SolrInputDocument child1 = parent.getChildDocuments().get(0); final SolrInputDocument child1 = childDocs1.get(0);
assertOnlyValue(s.apply("1.1"), child1, "id"); assertOnlyValue(s.apply("1.1"), child1, "id");
assertOnlyValue(s.apply("i am the 1st child"), child1, "name"); assertOnlyValue(s.apply("i am the 1st child"), child1, "name");
assertOnlyValue("child", child1,"cat"); assertOnlyValue("child", child1,"cat");
} }
{ {
final SolrInputDocument child2 = parent.getChildDocuments().get(1); final SolrInputDocument child2 = childDocs1.get(1);
assertOnlyValue(s.apply("1.2"), child2, "id"); assertOnlyValue(s.apply("1.2"), child2, "id");
assertOnlyValue("i am the 2nd child", child2, "name"); assertOnlyValue("i am the 2nd child", child2, "name");
assertOnlyValue("test-new-label", child2, "test_s");
assertOnlyValue("child", child2, "cat"); assertOnlyValue("child", child2, "cat");
assertEquals(1, child2.getChildDocuments().size()); List<SolrInputDocument> childDocs2;
final SolrInputDocument grandChild = child2.getChildDocuments().get(0); if(anonChildDocsFlag) {
childDocs2 = child2.getChildDocuments();
} else {
childDocs2 = (List) ((child2.getField("grandchildren")).getValue());
}
assertEquals(1, childDocs2.size());
final SolrInputDocument grandChild = childDocs2.get(0);
assertOnlyValue(s.apply("1.2.1"), grandChild,"id"); assertOnlyValue(s.apply("1.2.1"), grandChild,"id");
assertOnlyValue("i am the grandchild", grandChild, "name"); assertOnlyValue("i am the grandchild", grandChild, "name");
assertOnlyValue("grandchild", grandChild, "cat"); assertOnlyValue("grandchild", grandChild, "cat");
} }
} }
} }
}
private static void assertOnlyValue(String expected, SolrInputDocument doc, String field) { private static void assertOnlyValue(String expected, SolrInputDocument doc, String field) {
assertEquals(Collections.singletonList(expected), doc.getFieldValues(field)); assertEquals(Collections.singletonList(expected), doc.getFieldValues(field));
@ -790,9 +817,7 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
req.close(); req.close();
} }
@Test private static final String SIMPLE_JSON_CHILD_DOCS = "{\n" +
public void testSimpleChildDocs() throws Exception {
String str = "{\n" +
" \"add\": {\n" + " \"add\": {\n" +
" \"doc\": {\n" + " \"doc\": {\n" +
" \"id\": \"1\",\n" + " \"id\": \"1\",\n" +
@ -808,12 +833,18 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
" }\n" + " }\n" +
" }\n" + " }\n" +
"}"; "}";
checkTwoChildDocs(str);
@Test
public void testSimpleAnonymousChildDocs() throws Exception {
checkTwoAnonymousChildDocs(SIMPLE_JSON_CHILD_DOCS);
} }
@Test @Test
public void testDupKeysChildDocs() throws Exception { public void testSimpleChildDocs() throws Exception {
String str = "{\n" + checkTwoAnonymousChildDocs(SIMPLE_JSON_CHILD_DOCS, false);
}
private static final String DUP_KEYS_JSON_CHILD_DOCS = "{\n" +
" \"add\": {\n" + " \"add\": {\n" +
" \"doc\": {\n" + " \"doc\": {\n" +
" \"_childDocuments_\": [\n" + " \"_childDocuments_\": [\n" +
@ -832,11 +863,23 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
" }\n" + " }\n" +
" }\n" + " }\n" +
"}"; "}";
checkTwoChildDocs(str);
@Test
public void testDupKeysAnonymousChildDocs() throws Exception {
checkTwoAnonymousChildDocs(DUP_KEYS_JSON_CHILD_DOCS);
} }
private void checkTwoChildDocs(String rawJsonStr) throws Exception { @Test
SolrQueryRequest req = req("commit","true"); public void testDupKeysChildDocs() throws Exception {
checkTwoAnonymousChildDocs(DUP_KEYS_JSON_CHILD_DOCS, false);
}
private void checkTwoAnonymousChildDocs(String rawJsonStr) throws Exception {
checkTwoAnonymousChildDocs(rawJsonStr, true);
}
private void checkTwoAnonymousChildDocs(String rawJsonStr, boolean anonChildDocs) throws Exception {
SolrQueryRequest req = req("commit","true", ANONYMOUS_CHILD_DOCS, Boolean.toString(anonChildDocs));
SolrQueryResponse rsp = new SolrQueryResponse(); SolrQueryResponse rsp = new SolrQueryResponse();
BufferingRequestProcessor p = new BufferingRequestProcessor(null); BufferingRequestProcessor p = new BufferingRequestProcessor(null);
JsonLoader loader = new JsonLoader(); JsonLoader loader = new JsonLoader();
@ -849,11 +892,20 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
SolrInputField f = d.getField( "id" ); SolrInputField f = d.getField( "id" );
assertEquals("1", f.getValue()); assertEquals("1", f.getValue());
SolrInputDocument cd = d.getChildDocuments().get(0); SolrInputDocument cd;
if (anonChildDocs) {
cd = d.getChildDocuments().get(0);
} else {
cd = (SolrInputDocument) (d.getField("_childDocuments_")).getFirstValue();
}
SolrInputField cf = cd.getField( "id" ); SolrInputField cf = cd.getField( "id" );
assertEquals("2", cf.getValue()); assertEquals("2", cf.getValue());
if (anonChildDocs) {
cd = d.getChildDocuments().get(1); cd = d.getChildDocuments().get(1);
} else {
cd = (SolrInputDocument)((List)(d.getField("_childDocuments_")).getValue()).get(1);
}
cf = cd.getField( "id" ); cf = cd.getField( "id" );
assertEquals("3", cf.getValue()); assertEquals("3", cf.getValue());
cf = cd.getField( "foo_i" ); cf = cd.getField( "foo_i" );
@ -865,7 +917,7 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
} }
@Test @Test
public void testEmptyChildDocs() throws Exception { public void testEmptyAnonymousChildDocs() throws Exception {
String str = "{\n" + String str = "{\n" +
" \"add\": {\n" + " \"add\": {\n" +
" \"doc\": {\n" + " \"doc\": {\n" +
@ -893,7 +945,7 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
} }
@Test @Test
public void testGrandChildDocs() throws Exception { public void testAnonymousGrandChildDocs() throws Exception {
String str = "{\n" + String str = "{\n" +
" \"add\": {\n" + " \"add\": {\n" +
" \"doc\": {\n" + " \"doc\": {\n" +
@ -946,5 +998,87 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
} }
@Test
public void testChildDocs() throws Exception {
String str = "{\n" +
" \"add\": {\n" +
" \"doc\": {\n" +
" \"id\": \"1\",\n" +
" \"children\": [\n" +
" {\n" +
" \"id\": \"2\",\n" +
" \"foo_s\": \"Yaz\"\n" +
" },\n" +
" {\n" +
" \"id\": \"3\",\n" +
" \"foo_s\": \"Bar\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" }\n" +
"}";
SolrQueryRequest req = req("commit","true");
SolrQueryResponse rsp = new SolrQueryResponse();
BufferingRequestProcessor p = new BufferingRequestProcessor(null);
JsonLoader loader = new JsonLoader();
loader.load(req, rsp, new ContentStreamBase.StringStream(str), p);
assertEquals( 1, p.addCommands.size() );
AddUpdateCommand add = p.addCommands.get(0);
SolrInputDocument one = add.solrDoc;
assertEquals("1", one.getFieldValue("id"));
List<SolrInputDocument> children = (List) one.getFieldValues("children");
SolrInputDocument two = children.get(0);
assertEquals("2", two.getFieldValue("id"));
assertEquals("Yaz", two.getFieldValue("foo_s"));
SolrInputDocument three = children.get(1);
assertEquals("3", three.getFieldValue("id"));
assertEquals("Bar", three.getFieldValue("foo_s"));
req.close();
}
@Test
public void testSingleRelationalChildDoc() throws Exception {
String str = "{\n" +
" \"add\": {\n" +
" \"doc\": {\n" +
" \"id\": \"1\",\n" +
" \"child1\": \n" +
" {\n" +
" \"id\": \"2\",\n" +
" \"foo_s\": \"Yaz\"\n" +
" },\n" +
" }\n" +
" }\n" +
"}";
SolrQueryRequest req = req("commit","true");
SolrQueryResponse rsp = new SolrQueryResponse();
BufferingRequestProcessor p = new BufferingRequestProcessor(null);
JsonLoader loader = new JsonLoader();
loader.load(req, rsp, new ContentStreamBase.StringStream(str), p);
assertEquals( 1, p.addCommands.size() );
AddUpdateCommand add = p.addCommands.get(0);
SolrInputDocument one = add.solrDoc;
assertEquals("1", one.getFieldValue("id"));
assertTrue(one.keySet().contains("child1"));
SolrInputDocument two = (SolrInputDocument) one.getField("child1").getValue();
assertEquals("2", two.getFieldValue("id"));
assertEquals("Yaz", two.getFieldValue("foo_s"));
req.close();
}
} }

View File

@ -87,8 +87,8 @@ public class SolrInputField implements Iterable<Object>, Serializable
value = vals; value = vals;
} }
// Add the new values to a collection // Add the new values to a collection, if childDoc add as is without iteration
if( v instanceof Iterable ) { if( v instanceof Iterable && !(v instanceof SolrDocumentBase)) {
for( Object o : (Iterable<Object>)v ) { for( Object o : (Iterable<Object>)v ) {
vals.add( o ); vals.add( o );
} }

View File

@ -290,5 +290,11 @@ public interface CommonParams {
String JSON_MIME = "application/json"; String JSON_MIME = "application/json";
String JAVABIN_MIME = "application/javabin"; String JAVABIN_MIME = "application/javabin";
/**
* If set to true, child documents will be added as anonymous children into the _childDocuments list,
* else, child documents will be added to SolrInputDocument as field values according to their key name.
*/
String ANONYMOUS_CHILD_DOCS = "anonChildDocs";
} }

View File

@ -288,13 +288,13 @@ public class JsonRecordReader {
event = parser.nextEvent(); event = parser.nextEvent();
if (event == EOF) break; if (event == EOF) break;
if (event == OBJECT_START) { if (event == OBJECT_START) {
handleObjectStart(parser, handler, values, new Stack<>(), recordStarted, null); handleObjectStart(parser, handler, new LinkedHashMap<>(), new Stack<>(), recordStarted, null);
} else if (event == ARRAY_START) { } else if (event == ARRAY_START) {
for (; ; ) { for (; ; ) {
event = parser.nextEvent(); event = parser.nextEvent();
if (event == ARRAY_END) break; if (event == ARRAY_END) break;
if (event == OBJECT_START) { if (event == OBJECT_START) {
handleObjectStart(parser, handler, values, new Stack<>(), recordStarted, null); handleObjectStart(parser, handler, new LinkedHashMap<>(), new Stack<>(), recordStarted, null);
} }
} }
} }
@ -350,6 +350,10 @@ public class JsonRecordReader {
event = parser.nextEvent(); event = parser.nextEvent();
if (event == ARRAY_END) break; if (event == ARRAY_END) break;
if (event == OBJECT_START) { if (event == OBJECT_START) {
// if single item in array will still be added as array
if(!values.containsKey(name)) {
values.put(name, new ArrayList<>());
}
walkObject(); walkObject();
} }
} }
@ -359,7 +363,7 @@ public class JsonRecordReader {
void walkObject() throws IOException { void walkObject() throws IOException {
if (node.isChildRecord) { if (node.isChildRecord) {
node.handleObjectStart(parser, node.handleObjectStart(parser,
(record, path) -> addChildDoc2ParentDoc(record, values), (record, path) -> addChildDoc2ParentDoc(record, values, getPathSuffix(path)),
new LinkedHashMap<>(), new LinkedHashMap<>(),
new Stack<>(), new Stack<>(),
true, true,
@ -438,18 +442,18 @@ public class JsonRecordReader {
} }
} }
private void addChildDoc2ParentDoc(Map<String, Object> record, Map<String, Object> values) { private void addChildDoc2ParentDoc(Map<String, Object> record, Map<String, Object> values, String key) {
record = Utils.getDeepCopy(record, 2); record = Utils.getDeepCopy(record, 2);
Object oldVal = values.get(null); Object oldVal = values.get(key);
if (oldVal == null) { if (oldVal == null) {
values.put(null, record); values.put(key, record);
} else if (oldVal instanceof List) { } else if (oldVal instanceof List) {
((List) oldVal).add(record); ((List) oldVal).add(record);
} else { } else {
ArrayList l = new ArrayList(); ArrayList l = new ArrayList();
l.add(oldVal); l.add(oldVal);
l.add(record); l.add(record);
values.put(null, l); values.put(key, l);
} }
} }
@ -486,6 +490,12 @@ public class JsonRecordReader {
values.put(fieldName, l); values.put(fieldName, l);
} }
// returns the last key of the path
private String getPathSuffix(String path) {
int indexOf = path.lastIndexOf("/");
if (indexOf == -1) return path;
return path.substring(indexOf + 1);
}
@Override @Override
public String toString() { public String toString() {